Generate open graph meta tags

This commit is contained in:
Christoph Hagen
2025-01-15 22:04:48 +01:00
parent 4b448f3415
commit f6b868502d
21 changed files with 324 additions and 109 deletions

View File

@ -36,6 +36,14 @@ enum HeaderElement {
case description(String)
case ogTitle(String)
case ogDescription(String)
case ogImage(String)
case ogUrl(String)
case charset
case viewport
@ -44,30 +52,24 @@ enum HeaderElement {
var order: Int {
switch self {
case .charset:
return 1
case .robots:
return 2
case .viewport:
return 3
case .icon:
return 10
case .css(_, let order):
return order
case .js:
return 20
case .jsModule:
return 30
case .author:
return 100
case .title:
return 101
case .description:
return 102
case .charset: 1
case .robots: 2
case .viewport: 3
case .icon: 10
case .css(_, let order): order
case .js: 20
case .jsModule: 30
case .author: 100
case .title: 101
case .description: 102
case .ogTitle: 103
case .ogDescription: 104
case .ogImage: 105
case .ogUrl: 106
}
}
var file: FileResource? {
var requiredFile: FileResource? {
switch self {
case .icon(let file, _, _):
return file
@ -113,6 +115,14 @@ extension HeaderElement {
return "<title>\(title)</title>"
case .description(let description):
return "<meta name='description' content=\"\(description)\">"
case .ogTitle(let title):
return "<meta property='og:title' content='\(title)'>"
case .ogDescription(let description):
return "<meta property='og:description' content='\(description)'>"
case .ogImage(let image):
return "<meta property='og:image' content='\(image)'>"
case .ogUrl(let url):
return "<meta property='og:url' content='\(url)'>"
case .charset:
return "<meta charset='utf-8' />"
case .viewport:
@ -141,6 +151,14 @@ extension HeaderElement: CustomStringConvertible {
return "title"
case .description:
return "description"
case .ogTitle:
return "og:title"
case .ogDescription:
return "og:description"
case .ogImage:
return "og:image"
case .ogUrl:
return "og:url"
case .charset:
return "charset"
case .viewport:

View File

@ -24,9 +24,11 @@ final class FeedPageGenerator {
func generatePage(language: ContentLanguage,
posts: [FeedEntryData],
title: String,
title: String?,
description: String?,
showTitle: Bool,
image: FileResource?,
pageUrl: String,
pageTitle: String?,
pageNumber: Int,
totalPages: Int,
languageButtonUrl: String,
@ -44,10 +46,14 @@ final class FeedPageGenerator {
text: language.next.rawValue,
url: languageButtonUrl)
let imageUrl = image?.linkPreviewImage(results: results)
let pageHeader = PageHeader(
language: language,
title: title,
title: title ?? pageTitle,
description: description,
image: imageUrl,
pageUrl: pageUrl,
iconUrl: iconUrl,
languageButton: languageButton,
links: content.navigationBar(in: language),
@ -57,8 +63,8 @@ final class FeedPageGenerator {
let page = GenericPage(
header: pageHeader,
additionalFooter: footer) { content in
if showTitle {
content += "<h1 class='separated-headline'>\(title)</h1>"
if let pageTitle {
content += "<h1 class='separated-headline'>\(pageTitle)</h1>"
}
for post in posts {
content += FeedEntry(data: post).content

View File

@ -25,7 +25,7 @@ final class PageGenerator {
text: settings.emptyPageText).content
}
func generate(page: Page, language: ContentLanguage, results: PageGenerationResults) -> String? {
func generate(page: Page, language: ContentLanguage, results: PageGenerationResults, pageUrl: String) -> String? {
let contentGenerator = PageContentParser(
content: content,
language: language, results: results)
@ -51,7 +51,7 @@ final class PageGenerator {
}
let headers = makeHeaders(requiredItems: results.requiredHeaders, results: results)
results.require(files: headers.compactMap { $0.file })
results.require(files: headers.compactMap { $0.requiredFile })
let iconUrl = content.settings.navigation.localized(in: language).rootUrl
let languageUrl = page.absoluteUrl(in: language.next)
@ -59,10 +59,14 @@ final class PageGenerator {
text: language.next.rawValue,
url: languageUrl)
let imageUrl = localized.linkPreview.image?.linkPreviewImage(results: results)
let pageHeader = PageHeader(
language: language,
title: localized.linkPreview.title ?? localized.title,
description: localized.linkPreview.description,
image: imageUrl,
pageUrl: pageUrl,
iconUrl: iconUrl,
languageButton: languageButton,
links: content.navigationBar(in: language),

View File

@ -34,22 +34,45 @@ private struct TagHeaderContent {
let description: String?
let image: String?
let iconUrl: String
let links: [NavigationBar.Link]
let headers: Set<HeaderElement>
let baseUrl: String
let basePath: String
let websiteUrl: String
let localizedBaseUrl: String
private func url(pageNumber: Int) -> String {
baseUrl + "/\(pageNumber)"
/**
The path (relative to the output folder) for the html file
*/
func filePath(pageNumber: Int) -> String {
basePath + "/\(pageNumber).html"
}
func fileUrl(pageNumber: Int) -> String {
url(pageNumber: pageNumber) + ".html"
/**
The absolute url (without domain) for the language button of the page
*/
private func languageButtonUrl(page: Int) -> String {
guard page > 1 else {
return localizedBaseUrl
}
return localizedBaseUrl + "/\(page)"
}
/**
The full url (including domain) to the page.
*/
private func pageUrl(page: Int) -> String {
guard page > 1 else {
return websiteUrl + basePath
}
return websiteUrl + basePath + "/\(page)"
}
func pageHeader(pageNumber: Int) -> PageHeader {
@ -57,10 +80,12 @@ private struct TagHeaderContent {
language: language,
title: title,
description: description,
image: image,
pageUrl: pageUrl(page: pageNumber),
iconUrl: iconUrl,
languageButton: .init(
text: language.next.rawValue,
url: localizedBaseUrl + "/\(pageNumber)"),
url: languageButtonUrl(page: pageNumber)),
links: links,
headers: headers,
icons: [])
@ -83,14 +108,19 @@ final class TagOverviewGenerator {
func generatePages(tags: [Tag], overview: Tag) {
let localized = overview.localized(in: language)
let imageUrl = localized.linkPreview.image?.linkPreviewImage(results: results)
let header = TagHeaderContent(
language: language,
title: localized.linkPreview.title ?? localized.title,
description: localized.linkPreview.description,
image: imageUrl,
iconUrl: content.settings.navigation.localized(in: language).rootUrl,
links: content.navigationBar(in: language),
headers: content.postPageHeaders,
baseUrl: overview.absoluteUrl(in: language),
basePath: overview.absoluteUrl(in: language),
websiteUrl: content.settings.general.url,
localizedBaseUrl: overview.absoluteUrl(in: language.next))
// Sort tags by title
@ -140,16 +170,16 @@ final class TagOverviewGenerator {
}
if totalPages > 1 {
content += PostFeedPageNavigation(
linkPrefix: header.baseUrl,
linkPrefix: header.basePath,
currentPage: pageNumber,
numberOfPages: totalPages).content
}
}
let fileContent = page.content
let url = header.fileUrl(pageNumber: pageNumber)
let filePath = header.filePath(pageNumber: pageNumber)
guard content.storage.write(fileContent, to: url) else {
results.unsavedOutput(url, source: .tagOverview)
guard content.storage.write(fileContent, to: filePath) else {
results.unsavedOutput(filePath, source: .tagOverview)
return
}
}

View File

@ -7,20 +7,28 @@ struct FeedGeneratorSource: PostListPageGeneratorSource {
let results: PageGenerationResults
var showTitle: Bool {
false
}
var postsPerPage: Int {
content.settings.posts.postsPerPage
}
var pageTitle: String {
content.settings.posts.localized(in: language).title
var pageTitle: String? {
nil // Don't show title in page
}
var pageDescription: String {
content.settings.posts.localized(in: language).description
private var linkPreview: LinkPreview {
content.settings.posts.localized(in: language).linkPreview
}
var linkTitle: String? {
linkPreview.title
}
var linkDescription: String? {
linkPreview.description
}
var linkImage: FileResource? {
linkPreview.image
}
/**

View File

@ -17,7 +17,15 @@ final class PostListPageGenerator {
}
private func pageUrl(in language: ContentLanguage, pageNumber: Int) -> String {
"\(source.pageUrlPrefix(for: language))/\(pageNumber)"
let base = source.content.settings.general.url + source.pageUrlPrefix(for: language)
guard pageNumber > 1 else {
return base
}
return base + "/\(pageNumber)"
}
private func filePath(in language: ContentLanguage, pageNumber: Int) -> String {
"\(source.pageUrlPrefix(for: language))/\(pageNumber).html"
}
func createPages(for posts: [Post]) {
@ -82,18 +90,23 @@ final class PostListPageGenerator {
// Includes leading slash
let languageButtonUrl = pageUrl(in: language.next, pageNumber: pageIndex)
// Includes leading slash
let pageUrl = pageUrl(in: language, pageNumber: pageIndex)
let fileContent = feedPageGenerator.generatePage(
language: language,
posts: posts,
title: source.pageTitle,
description: source.pageDescription,
showTitle: source.showTitle,
title: source.linkTitle,
description: source.linkDescription,
image: source.linkImage,
pageUrl: pageUrl,
pageTitle: source.pageTitle,
pageNumber: pageIndex,
totalPages: pageCount,
languageButtonUrl: languageButtonUrl,
linkPrefix: source.pageUrlPrefix(for: language))
// Includes leading slash
let filePath = pageUrl(in: language, pageNumber: pageIndex) + ".html"
let filePath = self.filePath(in: language, pageNumber: pageIndex)
guard save(fileContent, to: filePath) else {
source.results.unsavedOutput(filePath, source: .feed)
return

View File

@ -7,11 +7,13 @@ protocol PostListPageGeneratorSource {
var results: PageGenerationResults { get }
var showTitle: Bool { get }
var pageTitle: String? { get }
var pageTitle: String { get }
var linkTitle: String? { get }
var pageDescription: String { get }
var linkDescription: String? { get }
var linkImage: FileResource? { get }
/**
The url to the page, including a leading slash

View File

@ -9,20 +9,28 @@ struct TagPageGeneratorSource: PostListPageGeneratorSource {
let tag: Tag
var showTitle: Bool {
true
}
var postsPerPage: Int {
content.settings.posts.postsPerPage
}
var pageTitle: String {
tag.localized(in: language).name
var pageTitle: String? {
linkPreview.title ?? tag.localized(in: language).name
}
var pageDescription: String {
tag.localized(in: language).linkPreview.description ?? ""
private var linkPreview: LinkPreview {
tag.localized(in: language).linkPreview
}
var linkTitle: String? {
linkPreview.title
}
var linkDescription: String? {
linkPreview.description
}
var linkImage: FileResource? {
linkPreview.image
}
func pageUrlPrefix(for language: ContentLanguage) -> String {