Skip to content

Commit 7a6f376

Browse files
committed
wip: add another post, change from Posts to DevLog
Add experimental support for applying styles via inlineStyle, and generate CSS classes
1 parent 3b6684a commit 7a6f376

File tree

9 files changed

+143
-47
lines changed

9 files changed

+143
-47
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ package.targets
9696
"-Xfrontend",
9797
"-warn-long-function-bodies=100",
9898
"-Xfrontend",
99-
"-warn-long-expression-type-checking=100"
99+
"-warn-long-expression-type-checking=1000"
100100
])
101101
]
102102
}

Sources/Pages/HomePage.swift

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public struct HomePage: Page {
4545
PostsSection()
4646
}
4747
Spacer()
48-
footer(.aria.label("Credits"), .class("wrapped"), .style("text-align: center;")) {
48+
footer(.aria.label("Credits"), .class("wrapped")) {
4949
div(.id("end-footer"), .class("container")) {
5050
p { "©\(Self.copyrightDateFormatter.string(from: Date.now)) Erik Bautista Santibanez" }
5151
p {
@@ -57,6 +57,7 @@ public struct HomePage: Page {
5757
}
5858
}
5959
}
60+
.inlineStyle("text-align", value: "center")
6061
}
6162
VueScript()
6263
}
@@ -75,9 +76,13 @@ public struct HomePage: Page {
7576
@Dependency(\.activityClient) private var activityClient
7677

7778
var content: some HTML {
78-
section(.id("hero"), .class("wrapped"), .aria.label("About")) {
79+
section(.id("user"), .class("wrapped"), .aria.label("About")) {
7980
div(.class("container")) {
80-
code(.class("code-tag")) { "User.swift" }
81+
pre {
82+
a(.href("#user")) {
83+
code(.class("code-tag")) { "User.swift" }
84+
}
85+
}
8186

8287
header(.class("code-header")) {
8388
pre {
@@ -174,17 +179,21 @@ public struct HomePage: Page {
174179

175180
private struct PostsSection: HTML {
176181
var content: some HTML {
177-
section(.id("posts"), .class("wrapped")) {
182+
section(.id("dev-logs"), .class("wrapped")) {
178183
div(.class("container")) {
179-
code(.class("code-tag")) { "Posts.swift" }
184+
pre {
185+
a(.href("#dev-logs")) {
186+
code(.class("code-tag")) { "DevLogs.swift" }
187+
}
188+
}
180189

181190
header(.class("code-header")) {
182191
pre(.class("hljs language-swift")) {
183192
code {
184193
"""
185-
/// Posts.swift
194+
/// DevLogs.swift
186195
/// Portfolio
187-
var posts: [Post]
196+
var logs: [DevLog] = await fetch(.all)
188197
"""
189198
}
190199
}
@@ -212,24 +221,42 @@ public struct HomePage: Page {
212221
for (idx, post) in Post.allCases.reversed().enumerated() {
213222
article(
214223
.class("post"),
224+
.id(post.slug),
215225
.v.show("!selection || selection == '\(post.kind.rawValue)'")
216226
) {
217227
header {
218-
section(.style("display: flex; align-items: baseline;")) {
219-
span(.class("post__date"), .style("flex-grow: 1")) { post.dateFormatted }
228+
section {
229+
span(.class("post__date")) { post.datePosted.uppercased() }
230+
.inlineStyle("flex-grow", value: "1")
220231

221232
pre {
222-
code(.class("hljs language-swift"), .style("font-size: 0.75em; color: #777; font-weight: 500")) {
223-
"post[\(idx)]"
233+
a(.href("#\(post.slug)")) {
234+
code(.class("hljs language-swift")) {
235+
"logs[\(idx)]"
236+
}
237+
.inlineStyle("font-size", value: "0.75em")
238+
.inlineStyle("color", value: "#777")
239+
.inlineStyle("font-weight", value: "500")
224240
}
225241
}
226242
}
243+
.inlineStyle("display", value: "flex")
244+
.inlineStyle("align-items", value: "baseline")
227245

228246
if let postHeader = post.header {
229247
section {
230248
switch postHeader {
231249
case let .link(link):
232-
EmptyHTML()
250+
a(
251+
.href(link),
252+
.target(.blank),
253+
.rel("noopener noreferrer"),
254+
.class("post__header")
255+
) {
256+
figure {
257+
// TODO: add OpenGraph link
258+
}
259+
}
233260
case let .image(asset, label):
234261
img(.src(asset.url.assetString), .class("post__header"), .custom(name: "alt", value: label), .aria.label(label))
235262
case let .video(asset):
@@ -332,6 +359,10 @@ extension HomePage {
332359
AnyProperty("line-height", "1.5")
333360
}
334361

362+
"pre a" => {
363+
AnyProperty("text-decoration", "none")
364+
}
365+
335366
"h1, h2, h3, h4, h5, figure, p, ol, ul, pre" => {
336367
AnyProperty("margin", "0")
337368
}
@@ -432,18 +463,12 @@ extension HomePage {
432463
}
433464

434465
@CSSBuilder func heroStyles() -> some Rule {
435-
// MARK: - Hero header
436-
437-
ID("hero") => {}
438-
439466
Class("hero__location") => {
440467
Color(.hex("#D0D0D0"))
441468
}
442469
}
443470

444471
@CSSBuilder func postTabsStyles() -> some Rule {
445-
ID("posts") => {}
446-
447472
Class("posts__tab") => {
448473
AnyProperty("list-style-type", "none")
449474
AnyProperty("margin", "0")
@@ -517,7 +542,7 @@ extension HomePage {
517542
AnyProperty("border-color", "#3A3A3A")
518543
AnyProperty("border-style", "solid")
519544
AnyProperty("border-width", "1.5px")
520-
AnyProperty("border-radius", "0.5rem")
545+
AnyProperty("border-radius", "0.85rem")
521546
}
522547

523548
Class("post__links") => {

Sources/Pages/Models/Post+AllCases.swift

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,46 @@ extension Post: CaseIterable {
88

99
return [
1010
Self(
11-
id: 1,
11+
header: .video(assets.assets.posts.wledAppDemo),
12+
title: "A WLED Client for iOS",
13+
content: """
14+
I built a native iOS app for [WLED](https://github.com/wled/WLED), an open-source LED controller for ESP32, to control my RGB LED strips.
15+
""",
16+
date: Date(month: 8, day: 4, year: 2022),
17+
kind: .project
18+
),
19+
Self(
1220
title: "PrismUI \u{2014} Controlling MSI RGB Keyboard on mac",
1321
content: """
1422
> TBD
1523
""",
16-
date: Date(timeIntervalSince1970: 1_663_225_200),
24+
date: Date(month: 9, day: 15, year: 2024),
1725
kind: .project
1826
),
1927
Self(
20-
id: 2,
2128
header: .image(assets.assets.projects.animeNow.anDiscover, label: "Anime Now! discover image"),
2229
title: "Anime Now! \u{2014} An iOS and macOS App",
2330
content: """
2431
> TBD
2532
""",
26-
date: Date(timeIntervalSince1970: 1_663_225_200), // Sep 15, 2025
33+
date: Date(month: 9, day: 15, year: 2024),
2734
kind: .project
2835
),
2936
Self(
30-
id: 3,
3137
title: "Mochi \u{2014} Content Viewer for iOS and macOS",
3238
content: """
3339
> TBD
3440
""",
35-
date: Date(timeIntervalSince1970: 1_663_225_200), // Sep 15, 2025
41+
date: Date(month: 9, day: 15, year: 2024),
3642
kind: .project
3743
),
3844
Self(
39-
id: 4,
40-
title: "Website Redesign!",
45+
title: "Website Redesign",
4146
content: """
4247
I finally decided to redesign my website. \
4348
To expand my skills with Swift, I decided to rebuild my portfolio in Swift.
4449
""",
45-
date: Date(timeIntervalSince1970: 1_738_483_200), // Feb 2, 2025
50+
date: Date(month: 2, day: 2, year: 2025),
4651
kind: .blog,
4752
links: [
4853
Post.Link(
@@ -53,5 +58,6 @@ extension Post: CaseIterable {
5358
]
5459
),
5560
]
61+
.sorted { $0.date < $1.date }
5662
}
5763
}

Sources/Pages/Models/Post.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import Foundation
22
import PublicAssets
33

4-
struct Post {
5-
let id: Int
4+
struct Post: Sendable {
65
var header: Header?
76
let title: String
87
let content: HTMLMarkdown
@@ -18,12 +17,20 @@ struct Post {
1817
return formatter
1918
}()
2019

21-
var dateFormatted: String {
22-
Self.dateCreatedFormatter.string(from: self.date).uppercased()
20+
var datePosted: String {
21+
Self.dateCreatedFormatter.string(from: self.date)
2322
}
2423

24+
private static let timestampFormatter = {
25+
let formatter = DateFormatter()
26+
formatter.locale = Locale(identifier: "en_US_POSIX")
27+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
28+
formatter.dateFormat = "yyyyMMdd"
29+
return formatter
30+
}()
31+
2532
var slug: String {
26-
"\(id)-\(self.title.split { !$0.isLetter && !$0.isNumber }.joined(separator: "-").lowercased())"
33+
"\(Self.timestampFormatter.string(from: self.date))-\(self.title.split { !$0.isLetter && !$0.isNumber }.joined(separator: "-").lowercased())"
2734
}
2835

2936
enum Header {
@@ -44,9 +51,7 @@ struct Post {
4451
let href: String
4552
let role: Role
4653

47-
var isExternal: Bool {
48-
self.href.hasPrefix("http")
49-
}
54+
var isExternal: Bool { !self.href.hasPrefix("/") }
5055

5156
enum Role: String, CaseIterable {
5257
case primary

Sources/Pages/Utils/Date+Utils.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Foundation
2+
3+
extension Date {
4+
init(month: Int, day: Int, year: Int) {
5+
let components = DateComponents(
6+
calendar: .init(identifier: .gregorian),
7+
timeZone: .gmt,
8+
year: year,
9+
month: month,
10+
day: day,
11+
hour: 8,
12+
minute: 0,
13+
second: 0
14+
)
15+
self = components.date ?? Date()
16+
}
17+
}

Sources/Pages/Utils/Elementary+AnyHTML.swift

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import Elementary
22

3-
struct AnyHTML: HTML, ExpressibleByStringLiteral {
4-
var base: any HTML
3+
struct AnyHTML: HTML, Sendable, ExpressibleByStringLiteral {
4+
var base: _SendableAnyHTMLBox
55

6-
init(_ base: any HTML) {
7-
self.base = base
6+
init(_ base: any HTML & Sendable) {
7+
self.base = _SendableAnyHTMLBox(base)
8+
}
9+
10+
init(_ base: sending any HTML) {
11+
self.base = _SendableAnyHTMLBox(base)
812
}
913

1014
init(stringLiteral value: StringLiteralType) {
11-
self.base = HTMLText(value)
15+
self.base = _SendableAnyHTMLBox(HTMLText(value))
1216
}
1317

1418
static func _render(
@@ -20,7 +24,11 @@ struct AnyHTML: HTML, ExpressibleByStringLiteral {
2024
try await T._render(html, into: &renderer, with: context)
2125
}
2226

23-
try await render(html.base, with: context)
27+
guard let html = html.base.tryTake() else {
28+
return
29+
}
30+
31+
try await render(html, with: context)
2432
}
2533

2634
static func _render(
@@ -32,6 +40,10 @@ struct AnyHTML: HTML, ExpressibleByStringLiteral {
3240
T._render(html, into: &renderer, with: context)
3341
}
3442

35-
render(html.base, with: context)
43+
guard let html = html.base.tryTake() else {
44+
return
45+
}
46+
47+
render(html, with: context)
3648
}
3749
}

Sources/Pages/Utils/Elementary+Markdown.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ private struct HTMLMarkdownConverter: MarkupVisitor {
283283
}
284284
}
285285

286-
struct HTMLMarkdown: HTML, ExpressibleByStringLiteral {
286+
struct HTMLMarkdown: HTML, ExpressibleByStringLiteral, Sendable {
287287
let markdown: String
288288
let content: AnyHTML
289289

@@ -304,12 +304,12 @@ struct HTMLMarkdown: HTML, ExpressibleByStringLiteral {
304304

305305
private extension HTMLBuilder {
306306
@_disfavoredOverload
307-
static func buildExpression(_ expression: any HTML) -> AnyHTML {
307+
static func buildExpression(_ expression: sending any HTML) -> AnyHTML {
308308
AnyHTML(expression)
309309
}
310310

311311
@_disfavoredOverload
312-
static func buildFinalResult(_ component: some HTML) -> AnyHTML {
312+
static func buildFinalResult(_ component: sending some HTML) -> AnyHTML {
313313
AnyHTML(component)
314314
}
315315
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Elementary
2+
3+
public struct _InlinableStyleElement<Content: HTML>: HTML {
4+
public typealias Tag = Content.Tag
5+
var wrappedContent: Content
6+
var name: String
7+
var value: String
8+
9+
public static func _render<Renderer: _AsyncHTMLRendering>(
10+
_ html: consuming Self,
11+
into renderer: inout Renderer,
12+
with context: consuming _RenderingContext
13+
) async throws {
14+
try await Content._render(html.wrappedContent, into: &renderer, with: context)
15+
}
16+
17+
public static func _render<Renderer: _HTMLRendering>(
18+
_ html: consuming Self,
19+
into renderer: inout Renderer,
20+
with context: consuming _RenderingContext
21+
) {
22+
Content._render(html.wrappedContent, into: &renderer, with: context)
23+
}
24+
}
25+
26+
extension HTML where Tag: HTMLTrait.Attributes.Global {
27+
func inlineStyle(_ name: String, value: String) -> _AttributedElement<Self> {
28+
self.attributes(.style("\(name): \(value);"))
29+
// _InlinableStyleElement(wrappedContent: self.attributes(.style("\(name): \(value);")), name: name, value: value)
30+
}
31+
}
Binary file not shown.

0 commit comments

Comments
 (0)