diff --git a/Package.resolved b/Package.resolved index d3d55a8b..6c2cce60 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/johnsundell/collectionConcurrencyKit.git", "state": { "branch": null, - "revision": "2e4984dcaed6432f4eff175f6616ba463428cd8a", - "version": "0.1.0" + "revision": "b4f23e24b5a1bff301efc5e70871083ca029ff95", + "version": "0.2.0" } }, { @@ -33,15 +33,15 @@ "repositoryURL": "https://github.com/johnsundell/ink.git", "state": { "branch": null, - "revision": "77c3d8953374a9cf5418ef0bd7108524999de85a", - "version": "0.5.1" + "revision": "bcc9f219900a62c4210e6db726035d7f03ae757b", + "version": "0.6.0" } }, { "package": "Plot", "repositoryURL": "https://github.com/johnsundell/plot.git", "state": { - "branch": null, + "branch": "0.9.0", "revision": "80612b34252188edbef280e5375e2fc5249ac770", "version": "0.9.0" } diff --git a/Resources/FoundationTheme/styles.css b/Resources/FoundationTheme/styles.css index a6858fc9..f08b6caa 100644 --- a/Resources/FoundationTheme/styles.css +++ b/Resources/FoundationTheme/styles.css @@ -102,7 +102,7 @@ a { } .tag-list { - margin-bottom: 15px; + margin-bottom: 10px; } .tag-list li, @@ -113,6 +113,7 @@ a { padding: 4px 6px; border-radius: 5px; margin-right: 5px; + margin-bottom: 5px; } .tag-list a, @@ -136,6 +137,7 @@ a { .all-tags li { font-size: 1.4em; margin-right: 10px; + margin-bottom: 10px; padding: 6px 10px; } diff --git a/Sources/Publish/API/Theme+Foundation.swift b/Sources/Publish/API/Theme+Foundation.swift index d815219b..39cdcbb4 100644 --- a/Sources/Publish/API/Theme+Foundation.swift +++ b/Sources/Publish/API/Theme+Foundation.swift @@ -24,7 +24,7 @@ private struct FoundationHTMLFactory: HTMLFactory { .lang(context.site.language), .head(for: index, on: context.site), .body { - SiteHeader(context: context, selectedSelectionID: nil) + SiteHeader(context: context, selectedSectionID: nil) Wrapper { H1(index.title) Paragraph(context.site.description) @@ -49,7 +49,7 @@ private struct FoundationHTMLFactory: HTMLFactory { .lang(context.site.language), .head(for: section, on: context.site), .body { - SiteHeader(context: context, selectedSelectionID: section.id) + SiteHeader(context: context, selectedSectionID: section.id) Wrapper { H1(section.title) ItemList(items: section.items, site: context.site) @@ -67,7 +67,7 @@ private struct FoundationHTMLFactory: HTMLFactory { .body( .class("item-page"), .components { - SiteHeader(context: context, selectedSelectionID: item.sectionID) + SiteHeader(context: context, selectedSectionID: item.sectionID) Wrapper { Article { Div(item.content.body).class("content") @@ -87,7 +87,7 @@ private struct FoundationHTMLFactory: HTMLFactory { .lang(context.site.language), .head(for: page, on: context.site), .body { - SiteHeader(context: context, selectedSelectionID: nil) + SiteHeader(context: context, selectedSectionID: nil) Wrapper(page.body) SiteFooter() } @@ -100,7 +100,7 @@ private struct FoundationHTMLFactory: HTMLFactory { .lang(context.site.language), .head(for: page, on: context.site), .body { - SiteHeader(context: context, selectedSelectionID: nil) + SiteHeader(context: context, selectedSectionID: nil) Wrapper { H1("Browse all tags") List(page.tags.sorted()) { tag in @@ -124,7 +124,7 @@ private struct FoundationHTMLFactory: HTMLFactory { .lang(context.site.language), .head(for: page, on: context.site), .body { - SiteHeader(context: context, selectedSelectionID: nil) + SiteHeader(context: context, selectedSectionID: nil) Wrapper { H1 { Text("Tagged with ") @@ -161,7 +161,7 @@ private struct Wrapper: ComponentContainer { private struct SiteHeader: Component { var context: PublishingContext - var selectedSelectionID: Site.SectionID? + var selectedSectionID: Site.SectionID? var body: Component { Header { @@ -184,7 +184,7 @@ private struct SiteHeader: Component { return Link(section.title, url: section.path.absoluteString ) - .class(sectionID == selectedSelectionID ? "selected" : "") + .class(sectionID == selectedSectionID ? "selected" : "") } } } diff --git a/Sources/Publish/API/Website.swift b/Sources/Publish/API/Website.swift index a44bd1e6..fd4c5b8c 100644 --- a/Sources/Publish/API/Website.swift +++ b/Sources/Publish/API/Website.swift @@ -130,6 +130,67 @@ public extension Website { semaphore.wait() return try result!.get() } + + /// Publish this website using a default pipeline. To build a completely + /// custom pipeline, use the `publish(using:)` method. + /// - parameter theme: The HTML theme to generate the website using. + /// - parameter indentation: How to indent the generated files. + /// - parameter path: Any specific path to generate the website at. + /// - parameter rssFeedSections: What sections to include in the site's RSS feed. + /// - parameter rssFeedConfig: The configuration to use for the site's RSS feed. + /// - parameter deploymentMethod: How to deploy the website. + /// - parameter additionalSteps: Any additional steps to add to the publishing + /// pipeline. Will be executed right before the HTML generation process begins. + /// - parameter plugins: Plugins to be installed at the start of the publishing process. + /// - parameter file: The file that this method is called from (auto-inserted). + /// - parameter line: The line that this method is called from (auto-inserted). + @discardableResult + func publish(withTheme theme: Theme, + indentation: Indentation.Kind? = nil, + at path: Path? = nil, + rssFeedSections: Set = Set(SectionID.allCases), + rssFeedConfig: RSSFeedConfiguration? = .default, + deployedUsing deploymentMethod: DeploymentMethod? = nil, + additionalSteps: [PublishingStep] = [], + plugins: [Plugin] = [], + file: StaticString = #file) async throws -> PublishedWebsite { + try await publish( + at: path, + using: [ + .group(plugins.map(PublishingStep.installPlugin)), + .optional(.copyResources()), + .addMarkdownFiles(), + .sortItems(by: \.date, order: .descending), + .group(additionalSteps), + .generateHTML(withTheme: theme, indentation: indentation), + .unwrap(rssFeedConfig) { config in + .generateRSSFeed( + including: rssFeedSections, + config: config + ) + }, + .generateSiteMap(indentedBy: indentation), + .unwrap(deploymentMethod, PublishingStep.deploy) + ], + file: file + ) + } + + /// Publish this website using a custom pipeline. + /// - parameter path: Any specific path to generate the website at. + /// - parameter steps: The steps to use to form the website's publishing pipeline. + /// - parameter file: The file that this method is called from (auto-inserted). + /// - parameter line: The line that this method is called from (auto-inserted). + @discardableResult + func publish(at path: Path? = nil, + using steps: [PublishingStep], + file: StaticString = #file) async throws -> PublishedWebsite { + let pipeline = PublishingPipeline( + steps: steps, + originFilePath: Path("\(file)") + ) + return try await pipeline.execute(for: self, at: path) + } } // MARK: - Paths and URLs diff --git a/Sources/Publish/Internal/SiteMapGenerator.swift b/Sources/Publish/Internal/SiteMapGenerator.swift index 02463eac..d624c1ca 100644 --- a/Sources/Publish/Internal/SiteMapGenerator.swift +++ b/Sources/Publish/Internal/SiteMapGenerator.swift @@ -43,7 +43,7 @@ private extension SiteMapGenerator { return .group( .url( - .loc(site.url(for: section)), + .loc(site.url(for: section).appendingPathComponent("/")), .changefreq(.daily), .priority(1.0), .lastmod(max( @@ -57,7 +57,7 @@ private extension SiteMapGenerator { } return .url( - .loc(site.url(for: item)), + .loc(site.url(for: item).appendingPathComponent("/")), .changefreq(.monthly), .priority(0.5), .lastmod(item.lastModified) @@ -71,7 +71,7 @@ private extension SiteMapGenerator { } return .url( - .loc(site.url(for: page)), + .loc(site.url(for: page).appendingPathComponent("/")), .changefreq(.monthly), .priority(0.5), .lastmod(page.lastModified) diff --git a/Tests/PublishTests/Tests/SiteMapGenerationTests.swift b/Tests/PublishTests/Tests/SiteMapGenerationTests.swift index c60cd126..6cd847a3 100644 --- a/Tests/PublishTests/Tests/SiteMapGenerationTests.swift +++ b/Tests/PublishTests/Tests/SiteMapGenerationTests.swift @@ -22,9 +22,9 @@ final class SiteMapGenerationTests: PublishTestCase { let siteMap = try file.readAsString() let expectedLocations = [ - "https://swiftbysundell.com/one", - "https://swiftbysundell.com/one/item", - "https://swiftbysundell.com/page" + "https://swiftbysundell.com/one/", + "https://swiftbysundell.com/one/item/", + "https://swiftbysundell.com/page/" ] for location in expectedLocations { @@ -56,19 +56,19 @@ final class SiteMapGenerationTests: PublishTestCase { let siteMap = try file.readAsString() let expectedLocations = [ - "https://swiftbysundell.com/one", - "https://swiftbysundell.com/one/itemA", - "https://swiftbysundell.com/three/itemE", - "https://swiftbysundell.com/pageA" + "https://swiftbysundell.com/one/", + "https://swiftbysundell.com/one/itemA/", + "https://swiftbysundell.com/three/itemE/", + "https://swiftbysundell.com/pageA/" ] let unexpectedLocations = [ - "https://swiftbysundell.com/one/itemB", - "https://swiftbysundell.com/two", - "https://swiftbysundell.com/two/itemC", - "https://swiftbysundell.com/two/itemD", - "https://swiftbysundell.com/three/posts/itemF", - "https://swiftbysundell.com/pageB" + "https://swiftbysundell.com/one/itemB/", + "https://swiftbysundell.com/two/", + "https://swiftbysundell.com/two/itemC/", + "https://swiftbysundell.com/two/itemD/", + "https://swiftbysundell.com/three/posts/itemF/", + "https://swiftbysundell.com/pageB/" ] for location in expectedLocations {