twostraws / ignite Goto Github PK
View Code? Open in Web Editor NEWA static site generator for Swift developers.
License: MIT License
A static site generator for Swift developers.
License: MIT License
Love the principles behind Ignite and excited to start using + hopefully contributing to it in some form.
My existing site is built using tailwindcss, which I've been really enjoying as an alternative to bootstrap for its point-of-use simplicity. In some ways I feel it's similar to SwiftUI, in that you're encouraged to attach styles to elements inline rather than building a centralized stylesheet.
The reason I want to start using Ignite is that I have a lot of content that I'd like to share between my application and my website, e.g. the app's Shorcuts documentation, and I'd love for my site to build in the same toolchain as my app so that I can be sure that my site's documentation always reflects the latest app state.
Before I dive too deep into the rabbit hole though, I wanted to pose the question here in case there's some guidance I can follow for exploring use of tailwindcss with Ignite? Am I holding the tool wrong to think that I should try to incorporate tailwindcss or ideas from it?
Sometimes you want data to be fetched from a remote server at publish time
protocol AsyncElement {
func render(context: PublishingContext) async -> String
}
A benefit of this option is that it's pretty intuitive to the consumer. They should just block/await until their data is ready.
struct SomeExampleData: AsyncData {
/// fields from a public REST resources
}
public struct SomeExample: InlineElement {
func loadData(context: PublishingContext) async throws -> any AsyncData {
// Can access other member variables
// Can access URLRequest
return SomeExampleData(…)
}
func render(context: PublishingContext, runtimeData: any AsyncData?) -> String {
guard let data = runtimeData else {
return "SomeExampleData Missing"
}
// ...
}
}
A downside of providing any Async APIs is that poor implementation could lead to slow rendering. Each time an Async Component is Encountered the whole subtree of components that are children of that node might be invalidated. Additionally, peer or parent components could be invalidated if their rendering depends on how many children they have. Think how you would implement an Outline
component that automatically handles all the headers, but is physically located at the top of the page?
Additionally, we probably would want a timeout at some-level to finish rendering despite in-progress async requests.
DataLoader
that generically had an async method to fetch the data, but this didn't seem to be better than Option A above.If you wanted to build an AutomaticOutline
component, you need a way for this component to depend on being run after other Async components have finished running. With either of the options above, we can ensure the data could be provided via an appropriate environment hook (see #2), but it doesn't have something to await
against since new outline entries could be discovered when previous async components are rendered.
One way to modify the Option A would be:
protocol AsyncElement {
// (…other things as before)
public(get) var asyncRenderGroup: AsyncRenderGroup
public(get) var asyncRenderRank: Int
}
enum AsyncRenderGroup {
case `default`
/// after other async elements in the `default` group
case last
}
protocol DefaultAsyncElement: AsyncElement {}
extension DefaultAsyncElement {
let asyncRenderGroup: AsyncRenderGroup = .default
let asyncRenderRank: Int = 0
}
Obviously, if we go with Option B or some other solution, then a different technique might be needed for this edge case.
Running IgniteStarter from within Xcode doesn't appear to replace the Build folder after the first run. If I do a simple change to the 'Hello World' text on the Home page and rebuild the site the resulting index page doesn't reflect the change. I also get a “.DS_Store” couldn’t be copied to “Build” because an item with the same name already exists message.
I also downloaded and installed ignite as a CLI. It installed fine and creates a website but fails to build it. After a longish delay where the dependencies are downloaded and compiled I get a build failed message. Second run is faster but I get another error:
% ignite build
⚙️ Building your site...
warning: could not determine XCTest paths: terminated(1): /usr/bin/xcrun --sdk macosx --show-sdk-platform-path output:
xcrun: error: unable to lookup item 'PlatformPath' from command line tools installation
xcrun: error: unable to lookup item 'PlatformPath' in SDK '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk'
[0/1] Planning build
Building for debugging...
[0/3] Write swift-version--1AB21518FC5DEDBE.txt
Build complete! (0.14s)
❌ Failed to build.
Is there something I am missing? Thanks!
Some Components depend on specific assets like CSS, JS, or other resources. It’d be great if there was a way for components to dynamically register those assets only if they’re used by the page they’re rendering into.
As an example, there’s some Ignite code that tries to figure out which languages are in use in order to add the correct prism
code for that language to the page. This relies currently on users configuring the languages at the page level.
With an appropriate API, the Prism CodeBlock component could instead inspect the language it’s provided as an attribute, and request that Ignite ensures the dependency for that language was included.
@depends(on: […])
RenderContext
method registerDependency
under the hood, these could be implemented using the willRender
method proposed elsewhere
An advantage of this approach is that Ignite’s core could be agnostic of what JS/CSS is in your page, and the Bootstrap-specific components would only pull in bootstrap if they’re used. The majority of Ignite’s components/Elements seem agnostic of Bootstrap.
I just noticed that the newline character treatment in Content (*md
) files isn't what I expected. Specifically, a "\n"
character within a Markdown paragraph is removed (and, by the way, so is any preceding space " "
character). I noticed this because the last word of line n
and the first word of line n+1
were being concatenated, viz:
.. the sunspot that was pictured in yesterday’s
animation isn’t the reappearance ..
renders as ".. the sunspot that was pictured in yesterday’sanimation isn’t the reappearance .."
I understand that, in Markdown, paragraphs are separated by two newlines (and I noticed @twostraws Markdown files are like that: one long line per para, with no 'hard folds'), but I thought single newlines were treated as whitespace. Other Markdown rendering software I use (including BBEdit and Xcode) behaves as I expect [though what I'm typing right now onto the GitHub issues page is a third variation of different]. I spent rather a frighting number of hours stepping through cmark-gfm
with the debugger to no avail, and am left with the uncomfortable feeling I've missed something obvious. [cmark-gfm
seems to have a plethora of typographic options (-- to —, etc), I did I miss such an option?]
Obviously, I can reformat my Markdown to resolve this very simply.
"The good thing about standards is that there as so many to choose from!" .. the Markdown quote above renders as:
".. the sunspot that was pictured in yesterday’sanimation isn’t the reappearance .." -- Ignite
".. the sunspot that was pictured in yesterday’s animation isn’t the reappearance .." -- BBEdit/Xcode
".. the sunspot that was pictured in yesterday’s
animation isn’t the reappearance .." -- GitHub
When an unknown url is passed in, it instead shows the home page. How do I show a 404 page instead?
Property wrappers to aid in code extractions and organization.
Internally Ignite has several ElementBuilder
typealias, it would be useful to have public facing property wrappers for them:
//ElementBuilder.swift
typealias BlockElementBuilder = ElementBuilder<BlockElement>
typealias HeadElementBuilder = ElementBuilder<HeadElement>
typealias HTMLRootElementBuilder = ElementBuilder<HTMLRootElement>
typealias InlineElementBuilder = ElementBuilder<InlineElement>
typealias PageElementBuilder = ElementBuilder<PageElement>
typealias StaticPageBuilder = ElementBuilder<any StaticPage>
typealias ContentPageBuilder = ElementBuilder<any ContentPage>
✅ These compile
func bottomInfoSection()->BlockElement{
Text("Thank you for reading this page")
.font(.title6)
.id("bottom-info")
}
func bottomInfoSection()->BlockElement{
Group{
let id:String = "bottom-info"
Text("Thank you for reading this page")
.font(.title6)
.id(id)
Script(code:"document.getElementById('\(id)').addEventListener('contextmenu',e=>alert(e.target.innerText))")
}
}
❌ But these don't
//Error: Missing return in instance method expected to return 'any BlockElement'
func bottomInfoSection()->BlockElement{
let id:String = "bottom-info"
Text("Thank you for reading this page")
.font(.title6)
.id(id)
}
func bottomInfoSection()->BlockElement{
let id:String = "bottom-info"
Text("Thank you for reading this page")
.font(.title6)
.id(id)
Script(code:"document.getElementById('\(id)').addEventListener('contextmenu',e=>alert(e.target.innerText))")
}
Something like:
@BlockElementBuilder
func bottomInfoSection()->some BlockElement{ ... }
@ElementBuilder
func bottomInfoSection()->some BlockElement{ ... }
I really love how simply you can start with the Eleventy static site generator, and would like to explore whether we can ease the entry requirements for Ignite at some point. This is just a research topic rather than a specific issue – something to explore!
At the moment it can put the text .bottom
, .top
, .overlay
(plus recent added .overlayCenter
).
Could it have the text .trailing
and .leading
(which would snap to .bottom
if .trailing
and .top
if .leading
when size get too small).
This could also have alignment for the text to be in the top, center, or bottom of the image.
Card(imageName: "/images/rug.jpg") {
Text(placeholderLength: 100)
}
.contentPosition(.trailing(alignment: .top)
Hello!
First of all, thanks Paul for this great work. I watched his video and I'm studying the framework code to better understand Swift and SwiftUI programming. It is valuable for studying but as Swift enthusiast I'm also want to use it in my real projects as well (I got a hammer, so everything should be nails ;) ). SSG is a very thin niche for me, so if I could leave it out a little it would be more practical. Also I think the framework could have more real use cases.
I'm sorry for this may be silly question. Can you advice me whether I can use this framework to run server-side code as well?
What needs to be changed for this? Or what needs to be added?
What would an architecture look like for generating HTML pages during the processing of each request?
What difficulties do you see?
Thank you!
Currently the Makefile is hardcoded to only install to /usr/local/bin
which is not always possible or desired.
It would be nice to have location controlled via shell variable or some other command line invocation
Hi
Im playing a bit of catch up here with Ignite but its an exciting project!
I am curious - is there a potential world where Vapor could serve dynamic web content using Ignite as the HTML rendering on the back end? So rather than serving statically rendered HTML, a Vapor end point could serve dynamically rendered Ignite HTML using its syntax?
Just curious, and there isnt a discussion tab here, so apologies for making an issue.
Ignite run command gives the following error:
ignite run --preview
❌ This site specifies a custom subfolder, so it can't be previewed locally.
I was able to run my project but suddenly got the above error. After a while it got fixed automatically and then again it returned. There is not much in the project and everything is mostly in the default state
I tried to add Ignite to a new Swift Command Line project and importing Ignite gives me a Package Resolution Failed error:
Ignite could not be resolved.
...Ignite
Updating from http://github.com/twostraws/Ignite
Failed to resolve dependencies Dependencies could not be resolved because root depend on 'ignite' 0.2.1..<1.0.0
I have very recent MacOS and Xcode and the IgniteStarter project work fine. Selecting the Add Anyway button gives me a single Ignite package but none of the other packages load and the Ignite package can't be opened.
How do I generate the og:image head tag for a static page?
It would be nice to have the Markdown parsed via a "MarkdownParserService" or some patches of HTML provided by an "HTMLProvider" protocol?
Perhaps that could even not be a site wide call? My "Recipes" Content folder could use the RecipeoParser package and "BandReviews" could use the MetalMarkerExtreme package...
Maybe the simplest would be a Block Element that would take in any "HTMLRepresentable", not just an already rendered include.
I currently use hugo (partially to keep myself from fussing), but I really miss the plugin architecture of WordPress. hugo's lack of plugins is why I'll probably move on. Maybe to Ignite! Thank you!
I'm not sure how feasible this could be, but it would be a great addition to be able to access jquery methods directly on any BlockElement and PageElement.
I make a lot of web extensions and userscripts, and whenever it comes to UI styling and layouts, I always include JQuery. It provides a lot of SwiftUI like simplicity and straight forwardness to quickly applying changes and styling to any element.
Some problem common in Ignite and especially in traditional html/css/javascript flow:
To my surprise, it wasn't too difficult to get jquery loaded externally and available for use to dynamically style the page using the Script
Element.
As quick example, I got the code below working in Ignite. It checks for jQuery, and creates a new element, styles it, clicking on it appends to itself, animates in, and fades out.
Script(file: "https://code.jquery.com/jquery-3.7.1.min.js")
<script type="text/javascript">
if(!jQuery){
alert("❌DEBUG: JQuery not loaded")
}else{
alert("✅DEBUG: JQuery successfully loaded");
let userAnswers = $(".questions-answered").has(".correctly-answered");
let correctAnswers = userAnswers.length;
let presentSuccessMsg = correctAnswers >= 10;
if(presentSuccessMsg){
$(document.createElement("div"))
.addClass("successMsg")
.css("opacity", 0.9)
.css("border-radius", "10px")
.css({
fontSize:"larger", fontWeight:"bolder",
zIndex: 9999, position: "fixed",
left:"calc(50% - 200px)", top: "calc(50% - 150px)",
filter: "blur(0.5px)", display: "inline-block",
height: "300px", width: "400px",
backgroundColor: "green", whiteSpace: "pre-line",
padding: 10, overflow: "scroll"
})
.text(`🎉Congrats! You've successfully answered ${ userAnswers.length } questions! 🎉`)
.click(function(e){
$(this).append(`\nYou've Clicked Me ${$(this).text().split("\n").length} Times!`);
})
.slideDown(1000 * 2)
.delay(1000 * 5)
.fadeOut(1000 * 2)
.appendTo(document.body)
}
}
</script>
Other than jquery, a much larger target would be, being able to reference/access the DOM element on a BlockElement
. This will enable writing logic javascript logic that cannot be done using swift.
I've managed to get some decent results by using an Id tag to each BlockElement, and accessing them inside in a Script
element on the page Head
, but it's very un-SwiftUI.
Otherwise, Ignite has been pretty incredible and has high potential.
Please let me know if there's a way to do this already and I just missed it!
I'd like to build components that render differently depending on their position in the Element Hierarchy.
Some concrete examples:
(Virtual HTML-style DOM tree)
<SectionWithHeader title="Outer" >
<SectionWithHeader title="1.1" />
<SectionWithHeader title="1.2">
<SectionWithHeader title="1.2.1" />
<SectionWithHeader title="1.2.2" />
</SectionWithHeader>
</SectionWithHeader>
In this example, the hypothetical SectionWithHeader
component would use the .environment()
-context to help generate the following html:
<div>
<h1>Outer</h1>
<div>
<h2>1.1</h2>
</div>
<div>
<h2>1.2</h2>
<div>
<h3>1.2.1</h3>
</div>
<div>
<h3>1.2.2</h3>
</div>
</div>
… but only if it can keep track of how deep the component is within the tree.
Notice: you could implement this component to automatically provide data for a statically generated Outline, with automatic anchor-links to each heading
In various past projects, I've found it useful to be able to preview a component, but rendered side-by-side with different contexts. One example is simultaneously viewing light/dark themes of the same component.
<ThemeTester>
…my content here.
</ThemeTester>
Output:
<div>
<b>Light Mode:</b>
…my content here.
</div>
<div>
<b>Dark Mode:</b>
…my content here.
</div>
Related use-cases:
Some browsers have some of these as a tools at the browser/debugger level, but I've found those to be pretty inconvenient.
If we implement something like the SwiftUI's Environment modifier or React's Context then we can provide necessary data for implementing these use cases above, and plenty of others not listed.
Would be very useful to add support for JavaScript execution via what would be <script>
in HTML. I understand Ignite is meant for static sites, but it could be useful for certain specific actions, like analytics, reload and other browser actions, etc. Already tried using Embed
to see if it would execute, and it doesn't.
I was just trying to get started with this project and found that the only documentation seems to live in the README file. While that's a good starting point, I still had lots of questions after fully reading it and it took me ~1 hour to fully discover all the available elements and features by scanning through the entire projects source code and checking the structure and comments in the Starter project.
Since most of the code already has some useful documentation, I think a DocC-rendered site with a couple articles to get people started would go a long way in helping people to quickly check if this project covers their needs or not. Since I'm sure you're busy with WWDC aftermath, I'm happy to help. I can do a similar setup to what I've done for of my open-source project HandySwift, see here. The documentation then would live on swiftpackageindex.com unless you want me to do a GitHub Pages-based deployment, which I've also done just recently for WWDC Notes, see here.
Please let me know if you have other plans for the docs or if I should continue with my suggestion.
To dismiss modal dialogs I would like to introduce a close button. It seems it is as easy as adding the case close
to Role
as Bootstrap will make a close button consisting of a simple cross from btn-close
. Would this be an appropriate approach or does this polite the Role
type in any way? I could also introduce a CloseButton
type which would lead to some duplication though.
The ExampleSite struct incorrectly uses
var baseTitle = " - My Awesome Site"
it should be
var titleSuffix = "- My Awesome Site"
Hello all, I would like to discuss whether i18n support can be added out of the box.
Hey @twostraws, I've just started checking out Ignite and am very excited to use it for hosting a static site/blog I'm putting together. Part of the design of my blog calls for a gradient mask over text like this, and I was trying to figure out if it's possible to support this in Ignite today.
I've gone ahead and looked at your support for Color
to try and figure out how it's implemented, but in my cursory glance haven't figured out how I would implement equivalent support for gradients.
I was wondering if:
Thank you so much, really excited to put something together using Ignite.
A neat little feature, especially for the navigation bar, would be a Blur
element or a SwiftUI Material
port to Ignite. Like in SwiftUI, where a background can be chosen as Material
and then pick a blur of a special thickness, it would be neat for Ignite to have such element, possibly integrated directly into .background
as well.
While accessibility labels and ARIA support seem to exist, many statically generated sites like app landing pages or documentation sites would be vastly more accessible if they were available in multiple languages. From what I understand, it would be already possible to create a view with buttons that link to separate subfolders of articles (such as en
, de-DE
etc.). But there are a few others things that need to be considered for a website with multi-language support:
language
value of the Site
based on the current language settings (this line)String
parameters such as for a sites description
or a buttons labelI know that this sounds like adding a dynamic layer to this "static" site generator. Localization in my opinion is for sure a "dynamic" aspect worth providing, but if implemented in a thoughtful manner, we can retain the static aspect by simply creating a duplicate of each page with only the localized texts changed. The language could be provided at root path level via the Language enums raw value, leading to URLs like https://ignitesamples.hackingwithswift.com/de/grid-examples/
. We could generate variants of all pages for each supported language and place them in the respective folder (e.g. de
) so everything still is static.
But we would need some JavaScript to set the language
value based on the path during page load, so things like the metadata have the correct language set. For things like the usage of localizedContains, while building the different language subfolders, we would need to set a custom locale with the current language being generated for accurate results.
As for the string table, I would suggest we use a subset of String Catalogs as we don't need all features, such as "Vary by device". We could even start without pluralization support to keep things simple. I'm happy to donate my Codable
struct to parse them, which I have already implemented for one of my apps. Updating that is simple enough in case Apple makes changes. It's versioned as well, so we should be able to notice easily. This year, they didn't change anything to the structure, so it should be pretty stable. But I'm happy to adjust the code whenever needed.
Using String Catalogs rather than a custom format has great advantages:
String(localized:)
to specify texts that should be localizedLocalizedStringKey
rather than String
where neededAs not everyone will need or want to localize their sites, we should keep this as opt-in and default to exactly the same behavior without any subpaths like /en
. Also, a default language should be set and used as a fallback for when a domain is called without any language subpath. I'm not sure if a safe redirect is even possible on static site calls, so the default locale files should just live in the root directory.
Please let me know what you think about adding localization support and also about my suggestion. I'm happy to do provide an initial version of localization support once you've approved that what I outlined makes sense to you.
I am trying to put social links in a footer with the logo and handle aligned horizontally. Ideas?
I have a clean Mac with Xcode 16 Beta 2 on MacOS18 Beta 2, tried following the instructions to build and run ignite and got a bunch of warning about swift-driver which I've not come across before. I didn't see reference to this in the readme (appols if I missed it) but unclear if this is something I need to install. Haven't yet had a chance to test this on Linux to see if it is a Mac thing or a general thing. Might be worth addressing this in the readme. I'm also hitting build issues which are being addressed in another thread but wanted to file this anyway.
warning: 'ignite': <unknown>:0: warning: using (deprecated) legacy driver, Swift installation does not contain swift-driver at: '/Library/Developer/CommandLineTools/usr/bin/swift-driver-new'
warning: 'swift-argument-parser': <unknown>:0: warning: using (deprecated) legacy driver, Swift installation does not contain swift-driver at: '/Library/Developer/CommandLineTools/usr/bin/swift-driver-new'
warning: 'swift-markdown': <unknown>:0: warning: using (deprecated) legacy driver, Swift installation does not contain swift-driver at: '/Library/Developer/CommandLineTools/usr/bin/swift-driver-new'
warning: 'swift-cmark': <unknown>:0: warning: using (deprecated) legacy driver, Swift installation does not contain swift-driver at: '/Library/Developer/CommandLineTools/usr/bin/swift-driver-new'
Hello,
is it possible to change the background of the Accordion title, when it is open, from blue to a self-defined value?
In my opinion, it would be helpful to maintain a colour style throughout the website.
Regards,
Ralf
I like to utilize FA in my site. However, the instructions for use are to copy this code into the : <script src="https://kit.fontawesome.com/xxxxxxxxxx.js" crossorigin="anonymous"></script>
How can I add this to the ? I don't see a way to modify that element.
Thank you for the wonderful work on this and I apologize if there is a simple fix that I am just missing.
Have already tested any Ignite websites for Search Engine Optimization ?
On my Site
definition, I had the layouts assigned like below:
var layouts = [
BlogPost()
]
where BlogPost
is a custom layout inheriting from ContentPage
.
When I run the ignite build
command, it finishes with the message ✅ Successfully built!
, but I look at the Build/
folder and the contents using the BlogPost
layout are not there.
I ran the executable scheme on Xcode and there I got an error Failed to find layout named BlogPost.
Should there be a difference between these two building options?
I think it would be nice to have the build
command reporting that error which I got reported when running the executable scheme on Xcode.
I am just starting to look at Ignite and my Nova editor gave an error/warning about the .js files added outside the HTML body. They are supposed to be in the head section or at the end of the body to speed the page render.
I see there is another issue to make conditional inclusions of .js only when needed for each page. I am adding this here as a stepping stone to at least get conformance.
public struct HTML: PageElement {
...
public func render(context: PublishingContext) -> String {
var output = "<!doctype html>"
output += "<html lang=\"\(context.site.language.rawValue)\" data-bs-theme=\"light\"\(attributes.description)>"
output += head?.render(context: context) ?? ""
output += body?.render(context: context) ?? ""
output += Script(file: "/js/bootstrap.bundle.min.js").render(context: context)
if context.site.syntaxHighlighters.isEmpty == false {
output += Script(file: "/js/syntax-highlighting.js").render(context: context)
}
// Activate tooltips if there are any.
if output.contains(#"data-bs-toggle="tooltip""#) {
output += Script(code: """
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
""").render(context: context)
}
output += "</html>"
return output
}
Some of the code should go into the body render function.
Positioning of overlay
Card(imageName: "/images/wind.jpg") {
Text("Arya, Samoyed")
.foregroundStyle(.white)
.fontWeight(.bold)
}
.contentPosition(.overlayCenter) // possible solution?
If the extension URL.packageDirectory(from file:)
can't find a file Package.swift in any parent directory it runs into an infinite loop. Hanging the whole publishing process before it even startet.
I'm trying to add a custom footer to my pages that spans 100% of the width of the screen. This has turned out to be more difficult than I'd expect because the Body
by default adds a <div class="col-sm-...">...</div>
around all of its content.
How can I remove this div from the body?
After building, the path generated in the index.html file is: /css/bootstrap.min.css instead of: css/bootstrap.min.css, which causes a loading issue with the style.
I thought this is interesting. It doesn't find the URL
extension when compiling with the Swift 6 keychain.
The output is:
Building for debugging...
[0/5] Write sources
[1/5] Write swift-version-6A63A117629E05AE.txt
error: emit-module command failed with exit code 1 (use -v to see invocation)
[3/7] Emitting module IgniteStarter
/Users/laurentb/Developer/Ignite/ExampleSite/Sources/Site.swift:20:15: error: ambiguous use of 'init(_:)'
18 | var name = "Hello World"
19 | var titleSuffix = "My Awesome Site"
20 | var url = URL("https://www.example.com")
| `- error: ambiguous use of 'init(_:)'
21 | var builtInIconsEnabled = true
Swift 5.10 has no problem with it!
I seem to enjoy exploring dark corners!
I added an old file, slightly edited to Markdown format, to my Contents and Ignite reported "Markdown could not be parsed" ..
Making a long story short, the code below, in MarkdownToHTML.swift
was failing on the try String(contentsOf: url)
call. The reason was that this was a seriously old file (from 1993), encoded as Mac OS Roman
and Swift couldn't make a String from it. I suspect the file actually violated that encoding so, strictly, failing to get a String out of it was correct.
public init(url: URL, removeTitleFromBody: Bool) throws {
do {
self.removeTitleFromBody = removeTitleFromBody
let markdown = try String(contentsOf: url) <--
let processed = processMetadata(for: markdown)
let document = Document(parsing: processed)
body = visit(document)
Annoyingly, I've not been able to reproduce the error yet (I fixed the file's encoding, and can't un-fix it!). When I can generate a 'bad encoding' file, or re-retrieve that old file from the shadows, and make the error happen in a test, I'll submit a patch. I think it'd be good for the throw/catch
to distinguish "garbage data" from "can't parse" especially in this case, where the file was visually perfect.
This is an awful arcane scenario, so much so it may not be worth changing anything to attend to it. I've entered it as an issue because (a) someone, someday may trip over it and read here for clues and (b) it's easy to eliminate any such future confusion, so I will (if @twostraws agrees).
Nice to meet you! Thank you for the wonderful performance at try!Swift. I'm fascinated by the Ignite presentations! 👏
I am currently working on creating and deploying a hosting server and web page using only Swift, by combining Vapor and Ignite. I tried to host a web page built with Ignite on Vapor and deploy it to Google Cloud Run, but encountered a strange issue. The issue is that the "Build" directory generated by Ignite is not uploaded for some reason when using the gcloud source upload command. (As a test, renaming it to "Workspace" or "Public" allowed successful uploads.) 🤔
Therefore, I would like to propose a fix to pass the buildDirectoryPath to the publish method of the Site.
// PublishingContext.swift
// Existing Implementation
init(for site: any Site, rootURL: URL) throws {
...
buildDirectory = rootDirectory.appending(path: "Build")
try parseContent()
}
init(for site: any Site, from file: StaticString) throws {
...
buildDirectory = rootDirectory.appending(path: "Build")
try parseContent()
}
// Alternative Implementation
init(for site: any Site, rootURL: URL, buildDirectoryPath: String = "Build") throws {
...
buildDirectory = rootDirectory.appending(path: buildDirectoryPath)
try parseContent()
}
init(for site: any Site, from file: StaticString, buildDirectoryPath: String = "Build") throws {
...
buildDirectory = rootDirectory.appending(path: buildDirectoryPath)
try parseContent()
}
// Site.swift
// Existing Implementation
public protocol Site {
...
func publish(from file: StaticString) throws
}
extension Site {
public func publish(from file: StaticString = #file) throws {
let context = try PublishingContext(for: self, from: file)
...
}
}
// Alternative Implementation
public protocol Site {
...
func publish(from file: StaticString, buildDirectoryPath: String) throws
}
extension Site {
...
public func publish(from file: StaticString = #file, buildDirectoryPath: String = "Build") throws {
let context = try PublishingContext(for: self, from: file, buildDirectoryPath: buildDirectoryPath)
...
}
}
let site = ExampleSite()
do {
try site.publish(buildDirectoryPath: "Public")
} catch {
print(error.localizedDescription)
}
Your consideration would be greatly appreciated 🙌
Hey @twostraws, thank you so much for such a great looking library, I've been playing around with it today in earnest and hoping to use it for building a couple of static websites!
I was trying to figure out the best way to deploy a website built with Ignite and didn't see any instructions so I decided to take the IgniteSamples project and use it's Build folder as a baseline for testing. I uploaded the folder directly to Cloudflare Pages (which serves static websites), including everything inside like the index.html, feed.rss, and all of the subdirectories to see if the process was that simple.
Unfortunately the result was a website that wouldn't load, and I still am not sure how to deploy an Ignite site. It's quite possible that I'm doing something incorrectly so I was wondering if you could perhaps share some advice or documentation on the matter?
Thank you again, looking forward to playing more with Ignite!
When working on with Markdown, there are sometimes alternate dialects that you'd like to handle. These might simply be as simple as the language-tags on code-blocks, as complicated as custom sorting tools for Markdown Tables, and often these are project specific tweaks that wouldn't be appropriate to merge into the core Ignite library.
In this module, there are a ton of visit*
methods that each render raw html.
https://github.com/twostraws/Ignite/blob/main/Sources/Ignite/Rendering/MarkdownToHTML.swift
It'd be great if there was a mapping step, where instead of directly emitting strings, users could hook full Ignite components.
// Current Implementation
func visitOrderedList(_ orderedList: Markdown.OrderedList) -> String {
var result = "<ol>"
for listItem in orderedList.listItems {
result += visit(listItem)
}
result += "</ol>"
return result
}
// Instead:
func visitOrderedList(_ orderedList: Markdown.OrderedList) -> Element {
return (tagOverrides.OrderedList ?? MarkdownOrderedList)(listItems: visit(orderedList.listItems))
}
// Where tagOverrides obviously needs some way to be populated by the project -- ideally even allowing for different patches in different content directories.
Note: I think this should emit Element
instead of String
to allow these components to play nice with #3 and #2 -- Otherwise Markdown pages wouldn't be able to have a hypothetical AutomaticOutline
static-component.
I have the following RegexBuilder:
import RegexBuilder
let regex = Regex {
"[img:"
Capture{
ZeroOrMore{
/./ ///<- Operator with postfix spacing cannot start a subexpression
}
}
"]"
}
.anchorsMatchLineEndings()
However for some reason I get an error when trying to build the IgniteStarter project with this specific regex included. This regex works fine in a new CLI project, in the Swift Playground, and in one of my own projects ( not based on Ignite ).
I am trying to capture the text between [img: Some Text] in a markdown file.
I tried a different RegexBuilder as an experiment and it worked fine.
I was going to ask about it on the Swift forums but since it only seems to happen with Ignite I thought I ask here first. I’m thinking it might be a package project thing but I’m not familiar enough with how packages and RegexBuilders work to see what might be going on and to formulate a decent question over there.
Anyway I thought you'd like to know!
I would like to introduce a SwiftUI-like aspectRatio(_:contentMode:)
function for images. For example to create square profile image where the source is not square. I found that object-fit-cover
on Bootstraps image class in combination with a Group
with the desired aspect ratio works great.
I'm wondering if this is the right approach and if an extension on BlockElement
with a type constraint for Image
is preferable or or a dedicated protocol. The latter has the advantage that object-fit-cover
only has an effect for image and video types.
enum ContentMode {
case fit, fill
var htmlClass: String {
"object-fit-\(self == .fill ? "cover" : "contain")"
}
}
extension BlockElement where Self == Image {
func aspectRation(_ ratio: AspectRatio, contentMode: ContentMode) -> some BlockElement {
Group {
self.class(contentMode.htmlClass)
}
.aspectRatio(ratio)
}
}
// OR
protocol MediaContent: BlockElement { }
extension Image: MediaContent { }
extension Video: MediaContent { }
extension MediaContent {
func aspectRation(_ ratio: AspectRatio, contentMode: ContentMode) -> some BlockElement {
Group {
self.class(contentMode.htmlClass)
}
.aspectRatio(ratio)
}
}
While I understand that .target(.blank)
open a new window I suggest a name change so that it would read better if .newWindow
At present
Link("HWS", target: "https://www.hackingwithswift.com")
.target(.blank)
Proposed
Link("HWS", target: "https://www.hackingwithswift.com")
.target(.newWindow)
or just add a new enum case for this with same to prevent breaking code already written
I'd like to use Ignite to build a sub-site that doesn't live directly under the root URL but under a subdirectory of that. I've hunted around to see if that is catered for in the existing code base but not found it (which might easily be a result of my not looking hard enough).
I saw the PR "Making the buildDirectory changeable #16" and thought that might be a way to accomplish my goal by setting PublishingContext(.., buildDirectoryPath="subdir/Build")
but, no. Similarly, I tried setting Site.url = https://example.com/subdir
but, no there too.
Building a site and placing all its files into a subdirectory doesn't work because assets, css, etc, being referenced as, /xxx
are not found because they're actually at /subdir/xxx
. I'm going to dig into this more and submit a PR, if necessary, but wanted to check that I hadn't missed something obvious before setting out.
I tried using a custom font on my website, and then I encountered issues. I'd be awesome to have something like var robotsConfiguration = Robots()
but for fonts, and then a Font()
object.
I was able to get it working by CSS, adding a .style
to the Theme()
and then another .style
on the text I want to change the font, but it's not an ideal solution, clearly a workaround.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.