Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enum for MetaTag properties and names outlined in #348. #373

Merged
merged 3 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 80 additions & 17 deletions Sources/Ignite/Elements/MetaTag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,54 @@ import Foundation
/// An item of metadata that helps browsers and search engines understand
/// your page better.
public struct MetaTag: HeadElement, Sendable {
/// Metadata names for social media cards.
public enum Name: String, Sendable {
/// The image to display in Twitter cards.
case twitterImage = "twitter:image"
/// The title to display in Twitter cards.
case twitterTitle = "twitter:title"
/// The domain to display in Twitter cards.
case twitterDomain = "twitter:domain"
/// The type of Twitter card to use.
case twitterCard = "twitter:card"
/// Whether to disable Twitter tracking.
case twitterDoNotTrack = "twitter:dnt"
/// The description to display in Twitter cards.
case twitterDescription = "twitter:description"
/// The software used to generate the document.
case generator
/// The viewport configuration for responsive web design.
case viewport

/// The meta attribute key.
var key: String { "name" }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the introduction of some more public-facing enums and decided to create #380. I'd like to have a conversation there to decide whether or not this is something we want to do instead


/// Metadata properties for social media sharing.
public enum Property: String, Sendable {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The distinction between Name and Property seems like a bagatelle, and means folks need to select the correct initializer in order to see code completion for the things they care about. Could we merge all these into a single enum, then use a computed property to return the key string based on a switch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent idea! Will revise 🙌🏼

/// The title of the shared content.
case openGraphTitle = "og:title"
/// A description of the shared content.
case openGraphDescription = "og:description"
/// The image to display when sharing.
case openGraphImage = "og:image"
/// The canonical URL of the shared content.
case openGraphURL = "og:url"
/// The name of the website.
case openGraphSiteName = "og:site_name"

/// The meta attribute key.
var key: String { "property" }
}

/// Allows mobile browsers to scale this page appropriately for the device.
public static let flexibleViewport = MetaTag(name: "viewport", content: "width=device-width, initial-scale=1")
public static let flexibleViewport = MetaTag(name: .viewport, content: "width=device-width, initial-scale=1")

/// Marks this page as using UTF-8 content.
public static let utf8 = MetaTag(characterSet: "utf-8")

/// Marks this page as having been automatically generated by Ignite.
public static let generator = MetaTag(name: "generator", content: Ignite.version)
public static let generator = MetaTag(name: .generator, content: Ignite.version)

/// The content and behavior of this HTML.
public var body: some HTML { self }
Expand All @@ -41,6 +81,18 @@ public struct MetaTag: HeadElement, Sendable {
/// A character set string that describes this page's content.
var charset: String

/// Creates a new `MetaTag` instance from the name and content provided.
/// - Parameters:
/// - name: The name for the metadata.
/// - content: The value for the metadata.
public init(name: Name, content: String) {
self.type = name.key

self.name = name.rawValue
self.content = content
self.charset = ""
}

/// Creates a new `MetaTag` instance from the name and content provided.
/// - Parameters:
/// - name: The name for the metadata.
Expand All @@ -59,10 +111,22 @@ public struct MetaTag: HeadElement, Sendable {
/// - Parameters:
/// - name: The name for the metadata.
/// - content: The URL value for the metadata.
public init(name: String, content: URL) {
public init(name: Name, content: URL) {
self.init(name: name, content: content.absoluteString)
}

/// Creates a new `MetaTag` instance from the property and content provided.
/// - Parameters:
/// - property: The property name for the metadata.
/// - content: The value for the metadata.
public init(property: Property, content: String) {
self.type = property.key

self.name = property.rawValue
self.content = content
self.charset = ""
}

/// Creates a new `MetaTag` instance from the property and content provided.
/// - Parameters:
/// - property: The property name for the metadata.
Expand All @@ -81,7 +145,7 @@ public struct MetaTag: HeadElement, Sendable {
/// - Parameters:
/// - property: The name for the metadata.
/// - content: The URL value for the metadata.
public init(property: String, content: URL) {
public init(property: Property, content: URL) {
self.init(property: property, content: content.absoluteString)
}

Expand All @@ -104,29 +168,29 @@ public struct MetaTag: HeadElement, Sendable {
/// into your page header to enable social sharing.
@ElementBuilder<MetaTag> public static func socialSharingTags(for page: Page) -> [MetaTag] {
let site = PublishingContext.default.site
MetaTag(property: "og:site_name", content: site.name)
MetaTag(property: .openGraphSiteName, content: site.name)

if let image = page.image {
MetaTag(property: "og:image", content: image)
MetaTag(property: "twitter:image", content: image)
MetaTag(property: .openGraphImage, content: image)
MetaTag(name: .twitterImage, content: image)
}

MetaTag(property: "og:title", content: page.title)
MetaTag(property: "twitter:title", content: page.title)
MetaTag(property: .openGraphTitle, content: page.title)
MetaTag(name: .twitterTitle, content: page.title)

if page.description.isEmpty == false {
MetaTag(property: "og:description", content: page.description)
MetaTag(name: "twitter:description", content: page.description)
MetaTag(property: .openGraphDescription, content: page.description)
MetaTag(name: .twitterDescription, content: page.description)
}

MetaTag(property: "og:url", content: page.url)
MetaTag(property: .openGraphURL, content: page.url)

if let domain = page.url.removingWWW {
MetaTag(name: "twitter:domain", content: domain)
MetaTag(name: .twitterDomain, content: domain)
}

MetaTag(name: "twitter:card", content: "summary_large_image")
MetaTag(name: "twitter:dnt", content: "on")
MetaTag(name: .twitterCard, content: "summary_large_image")
MetaTag(name: .twitterDoNotTrack, content: "on")
}

/// Renders this element using publishing context passed in.
Expand All @@ -138,8 +202,7 @@ public struct MetaTag: HeadElement, Sendable {
if charset.isEmpty {
attributes.append(customAttributes:
.init(name: type, value: name),
.init(name: "content", value: content)
)
.init(name: "content", value: content))
} else {
attributes.append(customAttributes:
.init(name: "charset", value: charset)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Ignite/Rendering/Attribute+Convenience.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// BooleanAttribute.swift
// Attributes+Convenience.swift
// Ignite
// https://www.github.com/twostraws/Ignite
// See LICENSE for license information.
Expand Down
48 changes: 24 additions & 24 deletions Tests/IgniteTesting/Elements/MetaTag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ struct MetaTagTests {
#expect(output == "<meta content=\"my content\" name=\"tagname\" />")
}

@Test("Meta tag with name a string and content a URL")
@Test("Meta tag with name an enum and content a URL")
func withNameStringAndContentURL() async throws {
let element = MetaTag(name: "tagname", content: URL(string: "https://example.com?s=searching#target")!)
let element = MetaTag(name: .twitterDomain, content: URL(string: "https://example.com?s=searching#target")!)

let output = element.render()

#expect(output == "<meta content=\"https://example.com?s=searching#target\" name=\"tagname\" />")
#expect(output == "<meta content=\"https://example.com?s=searching#target\" name=\"twitter:domain\" />")
}

@Test("Meta tag with property and content both strings")
Expand All @@ -45,13 +45,13 @@ struct MetaTagTests {
#expect(output == "<meta content=\"my value\" property=\"unique\" />")
}

@Test("Meta tag with property a string and content as URL")
@Test("Meta tag with property an enum and content as URL")
func withPropertyStringAndContentURL() async throws {
let element = MetaTag(property: "different", content: URL(string: "https://example.com?s=searching#target")!)
let element = MetaTag(property: .openGraphURL, content: URL(string: "https://example.com?s=searching#target")!)

let output = element.render()

#expect(output == "<meta content=\"https://example.com?s=searching#target\" property=\"different\" />")
#expect(output == "<meta content=\"https://example.com?s=searching#target\" property=\"og:url\" />")
}

@Test("Meta tag with character set only")
Expand Down Expand Up @@ -79,13 +79,13 @@ struct MetaTagTests {
let tags = MetaTag.socialSharingTags(for: page)

let expectedTags: [MetaTag] = [
MetaTag(property: "og:site_name", content: "My Test Site"),
MetaTag(property: "og:title", content: "My Page Title"),
MetaTag(property: "twitter:title", content: "My Page Title"),
MetaTag(property: "og:url", content: "https://example.com"),
MetaTag(property: "twitter:domain", content: "example.com"),
MetaTag(property: "twitter:card", content: "summary_large_image"),
MetaTag(property: "twitter:dnt", content: "on")
MetaTag(property: .openGraphSiteName, content: "My Test Site"),
MetaTag(property: .openGraphTitle, content: "My Page Title"),
MetaTag(name: .twitterTitle, content: "My Page Title"),
MetaTag(property: .openGraphURL, content: "https://example.com"),
MetaTag(name: .twitterDomain, content: "example.com"),
MetaTag(name: .twitterCard, content: "summary_large_image"),
MetaTag(name: .twitterDoNotTrack, content: "on")
]

#expect(tags.count == expectedTags.count)
Expand All @@ -108,17 +108,17 @@ struct MetaTagTests {
let actualTags = MetaTag.socialSharingTags(for: page)

let expectedTags: [MetaTag] = [
MetaTag(property: "og:site_name", content: "My Test Site"),
MetaTag(property: "og:image", content: "https://example.com/image.png"),
MetaTag(property: "twitter:image", content: "https://example.com/image.png"),
MetaTag(property: "og:title", content: "My Page Title"),
MetaTag(property: "twitter:title", content: "My Page Title"),
MetaTag(property: "og:description", content: "describing the page"),
MetaTag(property: "twitter:description", content: "describing the page"),
MetaTag(property: "og:url", content: "https://www.example.com"),
MetaTag(property: "twitter:domain", content: "example.com"),
MetaTag(property: "twitter:card", content: "summary_large_image"),
MetaTag(property: "twitter:dnt", content: "on")
MetaTag(property: .openGraphSiteName, content: "My Test Site"),
MetaTag(property: .openGraphImage, content: "https://example.com/image.png"),
MetaTag(name: .twitterImage, content: "https://example.com/image.png"),
MetaTag(property: .openGraphTitle, content: "My Page Title"),
MetaTag(name: .twitterTitle, content: "My Page Title"),
MetaTag(property: .openGraphDescription, content: "describing the page"),
MetaTag(name: .twitterDescription, content: "describing the page"),
MetaTag(property: .openGraphURL, content: "https://www.example.com"),
MetaTag(name: .twitterDomain, content: "example.com"),
MetaTag(name: .twitterCard, content: "summary_large_image"),
MetaTag(name: .twitterDoNotTrack, content: "on")
]

#expect(actualTags.count == expectedTags.count)
Expand Down