Skip to content

Commit efbfd49

Browse files
authored
RSS: Add option to customize an item’s link (#84)
This change makes it possible to specify a custom link for an item when it appears in an RSS feed. This is useful for RSS items that link to external resources, and enables those items to link directly to the target URL. However, the website’s own internal URL is still used as a default for each item’s GUID, in order to provide a stable identifier (as the external URL might change). That can always be customized using `rss.guid`, though.
1 parent ddf73c2 commit efbfd49

File tree

4 files changed

+36
-4
lines changed

4 files changed

+36
-4
lines changed

Sources/Publish/API/ItemRSSProperties.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import Foundation
1010
public struct ItemRSSProperties: Codable, Hashable {
1111
/// Any specific GUID that should be added for the item. When `nil`,
1212
/// the item's URL will be used and the `isPermaLink` attribute will
13-
/// be set to `true`. If not `nil`, a non-permalink will be assumed.
13+
/// be set to `true`, unless an explicit `link` was specified. If this
14+
/// property is not `nil`, then a non-permalink GUID will be assumed.
1415
public var guid: String?
1516
/// Any prefix that should be added to the item's title within an RSS feed.
1617
public var titlePrefix: String?
@@ -20,22 +21,31 @@ public struct ItemRSSProperties: Codable, Hashable {
2021
public var bodyPrefix: String?
2122
/// Any suffix that should be added to the item's body HTML within an RSS feed.
2223
public var bodySuffix: String?
24+
/// Any specific URL that the item should link to when inlcluded in an RSS
25+
/// feed. By default, the item's location on its website will be used. Note that
26+
/// this link won't be automatically used as the item's GUID, however, setting
27+
/// this property to a non-`nil` value will set the GUID's `isPermaLink` attribute
28+
/// to `false`.
29+
public var link: URL? = nil
2330

2431
/// Initialize an instance of this type
2532
/// - parameter guid: Any specific GUID that should be added for the item.
2633
/// - parameter titlePrefix: Any prefix that should be added to the item's title.
2734
/// - parameter titleSuffix: Any suffix that should be added to the item's title.
2835
/// - parameter bodyPrefix: Any prefix that should be added to the item's body HTML.
2936
/// - parameter bodySuffix: Any suffix that should be added to the item's body HTML.
37+
/// - parameter link: Any specific URL that the item should link to, other than its location.
3038
public init(guid: String? = nil,
3139
titlePrefix: String? = nil,
3240
titleSuffix: String? = nil,
3341
bodyPrefix: String? = nil,
34-
bodySuffix: String? = nil) {
42+
bodySuffix: String? = nil,
43+
link: URL? = nil) {
3544
self.guid = guid
3645
self.titlePrefix = titlePrefix
3746
self.titleSuffix = titleSuffix
3847
self.bodyPrefix = bodyPrefix
3948
self.bodySuffix = bodySuffix
49+
self.link = link
4050
}
4151
}

Sources/Publish/API/PlotComponents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ internal extension Node where Context: RSSItemContext {
149149
static func guid<T>(for item: Item<T>, site: T) -> Node {
150150
return .guid(
151151
.text(item.rssProperties.guid ?? site.url(for: item).absoluteString),
152-
.isPermaLink(item.rssProperties.guid == nil)
152+
.isPermaLink(item.rssProperties.guid == nil && item.rssProperties.link == nil)
153153
)
154154
}
155155

Sources/Publish/Internal/RSSFeedGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private extension RSSFeedGenerator {
7171
.guid(for: item, site: context.site),
7272
.title(item.rssTitle),
7373
.description(item.description),
74-
.link(context.site.url(for: item)),
74+
.link(item.rssProperties.link ?? context.site.url(for: item)),
7575
.pubDate(item.date, timeZone: context.dateFormatter.timeZone),
7676
.content(for: item, site: context.site)
7777
)

Tests/PublishTests/Tests/RSSFeedGenerationTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,27 @@ final class RSSFeedGenerationTests: PublishTestCase {
9696
"""))
9797
}
9898

99+
func testCustomItemLink() throws {
100+
let folder = try Folder.createTemporary()
101+
102+
try generateFeed(in: folder, content: [
103+
"one/item.md": """
104+
---
105+
rss.link: custom.link
106+
---
107+
Body
108+
"""
109+
])
110+
111+
let feed = try folder.file(at: "Output/feed.rss").readAsString()
112+
113+
XCTAssertTrue(feed.contains("<link>custom.link</link>"))
114+
115+
XCTAssertTrue(feed.contains("""
116+
<guid isPermaLink="false">https://swiftbysundell.com/one/item</guid>
117+
"""))
118+
}
119+
99120
func testReusingPreviousFeedIfNoItemsWereModified() throws {
100121
let folder = try Folder.createTemporary()
101122
let contentFile = try folder.createFile(at: "Content/one/item.md")
@@ -160,6 +181,7 @@ extension RSSFeedGenerationTests {
160181
("testConvertingRelativeLinksToAbsolute", testConvertingRelativeLinksToAbsolute),
161182
("testItemTitlePrefixAndSuffix", testItemTitlePrefixAndSuffix),
162183
("testItemBodyPrefixAndSuffix", testItemBodyPrefixAndSuffix),
184+
("testCustomItemLink", testCustomItemLink),
163185
("testReusingPreviousFeedIfNoItemsWereModified", testReusingPreviousFeedIfNoItemsWereModified),
164186
("testNotReusingPreviousFeedIfConfigChanged", testNotReusingPreviousFeedIfConfigChanged),
165187
("testNotReusingPreviousFeedIfItemWasAdded", testNotReusingPreviousFeedIfItemWasAdded)

0 commit comments

Comments
 (0)