Project updates from Template #1
@@ -8,7 +8,7 @@ The package provides, grouped by role:
|
|||||||
| Routing | `RouterController`, `RouteCollectionBuilder`, the `addController` extension on `RouterMethods` |
|
| Routing | `RouterController`, `RouteCollectionBuilder`, the `addController` extension on `RouterMethods` |
|
||||||
| Middlewares | `SecurityHeadersMiddleware`, `VaryMiddleware`, `RateLimitMiddleware`, `LocalizationMiddleware`, `NotFoundMiddleware` |
|
| Middlewares | `SecurityHeadersMiddleware`, `VaryMiddleware`, `RateLimitMiddleware`, `LocalizationMiddleware`, `NotFoundMiddleware` |
|
||||||
| Pages and assets | `Page`, `Asset`, `AssetExtension`, `FingerprintAssets` |
|
| Pages and assets | `Page`, `Asset`, `AssetExtension`, `FingerprintAssets` |
|
||||||
| Link previews | `SocialCard`, its `Image`, and the `SocialCardTag` meta tags it derives |
|
| Link previews | `SocialCard`, its `Image`, and the `Tag` meta tags it derives |
|
||||||
| Responses | `CachedHTMLResponse`, `LocalizedHTMLCollectionResponse` |
|
| Responses | `CachedHTMLResponse`, `LocalizedHTMLCollectionResponse` |
|
||||||
| Contexts | `LocalizedRequestContext` |
|
| Contexts | `LocalizedRequestContext` |
|
||||||
| Constants | The `HTTPField.Name` header names, `Int.RateLimit` limits, and `String.Security` header values the middlewares default to |
|
| Constants | The `HTTPField.Name` header names, `Int.RateLimit` limits, and `String.Security` header values the middlewares default to |
|
||||||
|
|||||||
@@ -70,52 +70,16 @@ public struct SocialCard: Sendable {
|
|||||||
|
|
||||||
/// The card's meta tags, in a stable order: the Open Graph type, site name, title, description, URL, and locale, then the image group, and
|
/// The card's meta tags, in a stable order: the Open Graph type, site name, title, description, URL, and locale, then the image group, and
|
||||||
/// the Twitter card style last. A tag whose fact the card does not carry is left out.
|
/// the Twitter card style last. A tag whose fact the card does not carry is left out.
|
||||||
public var tags: [SocialCardTag] {
|
public var tags: [Tag] {
|
||||||
let tags: [SocialCardTag?] = [
|
let tags: [Tag?] = [
|
||||||
SocialCardTag(
|
Tag(type, name: .type),
|
||||||
attribute: .property,
|
siteName.map { Tag($0, name: .siteName) },
|
||||||
content: type,
|
Tag(title, name: .title),
|
||||||
name: .type
|
summary.map { Tag($0, name: .description) },
|
||||||
),
|
url.map { Tag($0, name: .url) },
|
||||||
siteName.map {
|
locale.map { Tag($0, name: .locale) },
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: $0,
|
|
||||||
name: .siteName
|
|
||||||
)
|
|
||||||
},
|
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: title,
|
|
||||||
name: .title
|
|
||||||
),
|
|
||||||
summary.map {
|
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: $0,
|
|
||||||
name: .description
|
|
||||||
)
|
|
||||||
},
|
|
||||||
url.map {
|
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: $0,
|
|
||||||
name: .url
|
|
||||||
)
|
|
||||||
},
|
|
||||||
locale.map {
|
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: $0,
|
|
||||||
name: .locale
|
|
||||||
)
|
|
||||||
},
|
|
||||||
] + (image?.tags ?? []) + [
|
] + (image?.tags ?? []) + [
|
||||||
SocialCardTag(
|
Tag(style.rawValue, name: .twitter),
|
||||||
attribute: .name,
|
|
||||||
content: style.rawValue,
|
|
||||||
name: .twitter
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return tags.compactMap { $0 }
|
return tags.compactMap { $0 }
|
||||||
|
|||||||
@@ -39,30 +39,12 @@ extension SocialCard {
|
|||||||
// MARK: Computed
|
// MARK: Computed
|
||||||
|
|
||||||
/// The image's meta tags, in a stable order: its URL, width, and height, then its alt text when it carries one.
|
/// The image's meta tags, in a stable order: its URL, width, and height, then its alt text when it carries one.
|
||||||
public var tags: [SocialCardTag] {
|
public var tags: [Tag] {
|
||||||
let tags: [SocialCardTag?] = [
|
let tags: [Tag?] = [
|
||||||
SocialCardTag(
|
Tag(url, name: .image),
|
||||||
attribute: .property,
|
Tag(String(width), name: .imageWidth),
|
||||||
content: url,
|
Tag(String(height), name: .imageHeight),
|
||||||
name: .image
|
alt.map { Tag($0, name: .imageAlt) },
|
||||||
),
|
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: String(width),
|
|
||||||
name: .imageWidth
|
|
||||||
),
|
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: String(height),
|
|
||||||
name: .imageHeight
|
|
||||||
),
|
|
||||||
alt.map {
|
|
||||||
SocialCardTag(
|
|
||||||
attribute: .property,
|
|
||||||
content: $0,
|
|
||||||
name: .imageAlt
|
|
||||||
)
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return tags.compactMap { $0 }
|
return tags.compactMap { $0 }
|
||||||
|
|||||||
@@ -1,24 +1,44 @@
|
|||||||
/// A head meta tag of a ``SocialCard``: which attribute keys it, and its name and content.
|
extension SocialCard {
|
||||||
public struct SocialCardTag: Equatable, Sendable {
|
/// A head meta tag of a ``SocialCard``: its name and content, keyed by the attribute its ``name`` dictates.
|
||||||
|
public struct Tag: Equatable, Sendable {
|
||||||
|
|
||||||
// MARK: Properties
|
// MARK: Properties
|
||||||
|
|
||||||
/// The attribute the tag is keyed by.
|
/// The tag's value.
|
||||||
public let attribute: Attribute
|
public let content: String
|
||||||
|
|
||||||
/// The tag's value.
|
/// The tag's name.
|
||||||
public let content: String
|
public let name: Name
|
||||||
|
|
||||||
|
// MARK: Initializers
|
||||||
|
|
||||||
/// The tag's name.
|
/// Creates a head meta tag.
|
||||||
public let name: Name
|
/// - Parameters:
|
||||||
|
/// - content: the tag's value.
|
||||||
|
/// - name: the tag's name, dictating the attribute the tag is keyed by.
|
||||||
|
public init(
|
||||||
|
_ content: String,
|
||||||
|
name: Name
|
||||||
|
) {
|
||||||
|
self.content = content
|
||||||
|
self.name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
/// The attribute the tag is keyed by, dictated by its ``name``.
|
||||||
|
public var attribute: Attribute {
|
||||||
|
name.attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Enumerations
|
// MARK: - Enumerations
|
||||||
|
|
||||||
extension SocialCardTag {
|
extension SocialCard.Tag {
|
||||||
|
|
||||||
/// The meta attribute a ``SocialCardTag`` is keyed by, named by its raw value.
|
/// The meta attribute a ``SocialCard/Tag`` is keyed by, named by its raw value.
|
||||||
public enum Attribute: String, Sendable {
|
public enum Attribute: String, Sendable {
|
||||||
/// The `name` attribute, keying the Twitter tags.
|
/// The `name` attribute, keying the Twitter tags.
|
||||||
case name
|
case name
|
||||||
@@ -26,7 +46,7 @@ extension SocialCardTag {
|
|||||||
case property
|
case property
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name of a ``SocialCardTag``, carried in its raw value.
|
/// The name of a ``SocialCard/Tag``, carried in its raw value.
|
||||||
public enum Name: String, Sendable {
|
public enum Name: String, Sendable {
|
||||||
/// The `og:description` tag, carrying the card's summary.
|
/// The `og:description` tag, carrying the card's summary.
|
||||||
case description = "og:description"
|
case description = "og:description"
|
||||||
@@ -34,10 +54,10 @@ extension SocialCardTag {
|
|||||||
case image = "og:image"
|
case image = "og:image"
|
||||||
/// The `og:image:alt` tag, carrying the share image's text for assistive technologies.
|
/// The `og:image:alt` tag, carrying the share image's text for assistive technologies.
|
||||||
case imageAlt = "og:image:alt"
|
case imageAlt = "og:image:alt"
|
||||||
/// The `og:image:width` tag, carrying the share image's width in pixels.
|
|
||||||
case imageWidth = "og:image:width"
|
|
||||||
/// The `og:image:height` tag, carrying the share image's height in pixels.
|
/// The `og:image:height` tag, carrying the share image's height in pixels.
|
||||||
case imageHeight = "og:image:height"
|
case imageHeight = "og:image:height"
|
||||||
|
/// The `og:image:width` tag, carrying the share image's width in pixels.
|
||||||
|
case imageWidth = "og:image:width"
|
||||||
/// The `og:locale` tag, carrying the locale of the card's text.
|
/// The `og:locale` tag, carrying the locale of the card's text.
|
||||||
case locale = "og:locale"
|
case locale = "og:locale"
|
||||||
/// The `og:site_name` tag, carrying the name of the site the card belongs to.
|
/// The `og:site_name` tag, carrying the name of the site the card belongs to.
|
||||||
@@ -51,5 +71,18 @@ extension SocialCardTag {
|
|||||||
/// The `og:url` tag, carrying the absolute URL the card's page is served at.
|
/// The `og:url` tag, carrying the absolute URL the card's page is served at.
|
||||||
case url = "og:url"
|
case url = "og:url"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Implementations
|
||||||
|
|
||||||
|
public extension SocialCard.Tag.Name {
|
||||||
|
|
||||||
|
// MARK: Computed
|
||||||
|
|
||||||
|
/// The attribute keying a tag with this name: `property` for the Open Graph names, `name` for the Twitter ones.
|
||||||
|
var attribute: SocialCard.Tag.Attribute {
|
||||||
|
self == .twitter ? .name : .property
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ struct SocialCardTests {
|
|||||||
)
|
)
|
||||||
|
|
||||||
#expect(card.tags == [
|
#expect(card.tags == [
|
||||||
.init(attribute: .property, content: "website", name: .type),
|
.init("website", name: .type),
|
||||||
.init(attribute: .property, content: "A Site", name: .siteName),
|
.init("A Site", name: .siteName),
|
||||||
.init(attribute: .property, content: "A Title", name: .title),
|
.init("A Title", name: .title),
|
||||||
.init(attribute: .property, content: "A summary.", name: .description),
|
.init("A summary.", name: .description),
|
||||||
.init(attribute: .property, content: "https://site.example/", name: .url),
|
.init("https://site.example/", name: .url),
|
||||||
.init(attribute: .property, content: "en", name: .locale),
|
.init("en", name: .locale),
|
||||||
.init(attribute: .property, content: "https://site.example/img/card.png", name: .image),
|
.init("https://site.example/img/card.png", name: .image),
|
||||||
.init(attribute: .property, content: "2400", name: .imageWidth),
|
.init("2400", name: .imageWidth),
|
||||||
.init(attribute: .property, content: "1260", name: .imageHeight),
|
.init("1260", name: .imageHeight),
|
||||||
.init(attribute: .property, content: "An image.", name: .imageAlt),
|
.init("An image.", name: .imageAlt),
|
||||||
.init(attribute: .name, content: "summary_large_image", name: .twitter),
|
.init("summary_large_image", name: .twitter),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,9 +46,9 @@ struct SocialCardTests {
|
|||||||
let card = SocialCard(title: "A Title")
|
let card = SocialCard(title: "A Title")
|
||||||
|
|
||||||
#expect(card.tags == [
|
#expect(card.tags == [
|
||||||
.init(attribute: .property, content: "website", name: .type),
|
.init("website", name: .type),
|
||||||
.init(attribute: .property, content: "A Title", name: .title),
|
.init("A Title", name: .title),
|
||||||
.init(attribute: .name, content: "summary_large_image", name: .twitter),
|
.init("summary_large_image", name: .twitter),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +77,15 @@ struct SocialCardTests {
|
|||||||
style: .summary
|
style: .summary
|
||||||
)
|
)
|
||||||
|
|
||||||
#expect(card.tags.contains(.init(attribute: .property, content: "article", name: .type)))
|
#expect(card.tags.contains(.init("article", name: .type)))
|
||||||
#expect(card.tags.contains(.init(attribute: .name, content: "summary", name: .twitter)))
|
#expect(card.tags.contains(.init("summary", name: .twitter)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `keys a tag by the attribute its name dictates`() {
|
||||||
|
#expect(SocialCard.Tag.Name.twitter.attribute == .name)
|
||||||
|
#expect(SocialCard.Tag.Name.title.attribute == .property)
|
||||||
|
#expect(SocialCard.Tag("website", name: .type).attribute == .property)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user