Skip to content

Commit 00a7ab9

Browse files
committed
feat: add markdown codelang support
also add favicons, opengraph attrs
1 parent 48350f7 commit 00a7ab9

17 files changed

+309
-150
lines changed

Sources/App/Middlewares/SiteMiddleware.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ private struct NotFoundMiddleware<Context: RequestContext>: RouterMiddleware {
8282
}
8383

8484
fileprivate extension CodeLang {
85-
static func resolve(_ req: Request) -> CodeLang {
85+
static func resolve(_ req: Request) -> CodeLang? {
8686
req.uri.queryParameters["codeLang"]
87-
.flatMap { CodeLang(rawValue: $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()) }
88-
?? .swift
87+
.flatMap {
88+
$0 == "markdown" || $0 == "md" ? nil :
89+
CodeLang(rawValue: $0.trimmingCharacters(in: .whitespacesAndNewlines).lowercased())
90+
}
8991
}
9092
}

Sources/Pages/Components/FooterView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ struct FooterView: HTML {
1919
a(.target(.blank), .rel("noopener noreferrer"), .href("https://swift.org")) { "Swift" }
2020
" + "
2121
a(.target(.blank), .rel("noopener noreferrer"), .href("https://hummingbird.codes")) { "Hummingbird" }
22-
"."
22+
" + "
23+
a(.target(.blank), .rel("noopener noreferrer"), .href("https://github.com/vuejs/petite-vue")) { "petite-vue" }
2324
}
2425
}
2526
.containerStyling()

Sources/Pages/Components/HeaderView.swift

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import HTML
44
import Vue
55

66
struct HeaderView: HTML {
7-
let selected: Vue.Expression<CodeLang>
7+
let selected: Vue.Expression<CodeLang?>
88

99
var body: some HTML {
1010
header {
1111
hgroup {
1212
a(.href("/")) {
13-
code { "erikb.dev()" }
13+
code { "erikb.dev();" }
1414
.inlineStyle("font-size", "0.84em")
1515
.inlineStyle("color", "#AAA")
1616
.inlineStyle("font-weight", "bold")
@@ -31,13 +31,13 @@ struct HeaderView: HTML {
3131
}
3232

3333
private struct CodeSelector: HTML {
34-
let selected: Vue.Expression<CodeLang>
34+
let selected: Vue.Expression<CodeLang?>
3535

3636
var body: some HTML {
3737
#VueScope(false) { visible in
3838
button(
39-
.v.on(.click, visible.assign(!visible)),
40-
.v.bind(attrOrProp: "style", Expression(rawValue: "\(visible) ? { background: '#8A8A8A', color: '#080808' } : null"))
39+
.v.on(.click, visible.assign(!visible)),
40+
.v.bind(attrOrProp: "aria-pressed", visible)
4141
) {
4242
code { "</>" }
4343
}
@@ -49,17 +49,21 @@ private struct CodeSelector: HTML {
4949
.inlineStyle("padding", "0.28rem 0.4rem")
5050
.inlineStyle("color", "#AAA")
5151
.inlineStyle("cursor", "pointer")
52+
.inlineStyle("background", "#8A8A8A", post: "[aria-pressed=\"true\"]")
53+
.inlineStyle("color", "#080808", post: "[aria-pressed=\"true\"]")
5254

5355
ul(.hidden, .v.bind(attrOrProp: "hidden", !visible)) {
54-
for code in CodeLang.allCases {
56+
for codeLang in [nil] + CodeLang.allCases {
5557
li {
5658
button(
57-
.v.on(.click, selected.assign(Expression(code))),
58-
.v.on(.click, modifiers: .prevent, visible.assign(!visible)),
59-
.v.bind(attrOrProp: "style", Expression(rawValue: "{ background: \(selected) == \(Expression(code)) ? '#3A3A3A' : undefined }"))
59+
.v.on(
60+
.click,
61+
Expression(rawValue: "\(selected.assign(Expression(codeLang))), \(visible.assign(!visible))")
62+
),
63+
.v.bind(attrOrProp: "aria-selected", selected == Expression(codeLang))
6064
) {
6165
p {
62-
code.title
66+
codeLang?.title ?? "Markdown"
6367
}
6468
.inlineStyle("width", "100%")
6569
.inlineStyle("padding", "0.5rem")
@@ -69,19 +73,21 @@ private struct CodeSelector: HTML {
6973
.inlineStyle("width", "100%")
7074
.inlineStyle("cursor", "pointer")
7175
.inlineStyle("border-radius", "0.75rem")
72-
.inlineStyle("background", "#4A4A4A", post: ":hover")
76+
.inlineStyle("background", "#3A3A3A", post: ":hover")
77+
.inlineStyle("background", "#4A4A4A", post: "[aria-selected=\"true\"]")
7378
}
7479
.inlineStyle("overflow", "hidden")
7580
}
7681
}
82+
.inlineStyle("margin-top", "0.375rem", post: " > li:not(:first-child)")
7783
.inlineStyle("position", "absolute")
7884
.inlineStyle("right", "0")
7985
.inlineStyle("list-style", "none")
8086
.inlineStyle("padding", "0.4rem")
81-
.inlineStyle("margin-top", "0.25rem")
87+
// .inlineStyle("margin-top", "0.25rem")
8288
.inlineStyle("background", "#2A2A2A")
8389
.inlineStyle("border-radius", "1rem")
84-
.inlineStyle("font-size", "1.125em")
90+
// .inlineStyle("font-size", "1.125em")
8591
}
8692
.inlineStyle("position", "relative")
8793
}

Sources/Pages/Components/Page.swift

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@ import ConcurrencyExtras
22
import Dependencies
33
import HTML
44
import Hummingbird
5+
import PublicAssets
56
import Vue
67

78
public protocol Page: Sendable, HTMLDocument, ResponseGenerator {
89
var title: String { get }
10+
var desc: String { get }
911
var lang: String { get }
1012

1113
var chunkSize: Int { get }
1214
var headers: HTTPFields { get }
1315
}
1416

1517
extension Page {
18+
public var title: String { "Erik Bautista Santibanez" }
19+
public var desc: String {
20+
"""
21+
"""
22+
}
1623
public var lang: String { "en" }
1724
public var chunkSize: Int { 1024 }
1825
public var headers: HTTPFields { [.contentType: "text/html; charset=utf-8"] }
@@ -21,6 +28,8 @@ extension Page {
2128
_ document: consuming Self,
2229
into output: inout Output
2330
) {
31+
@Dependency(\.publicAssets) var assets
32+
2433
withDependencies {
2534
$0.ssg = .groupedStyles
2635
} operation: {
@@ -30,6 +39,29 @@ extension Page {
3039
meta(.charset(.utf8))
3140
tag("title") { document.title }
3241
meta(.name("viewport"), .content("width=device-width, initial-scale=1.0"))
42+
meta(.name("robots"), .content("index, follow"))
43+
44+
// opengraph meta
45+
meta(.property("og:title"), .content(document.title))
46+
meta(.property("og:description"), .content(document.desc))
47+
// TODO: move to env-var
48+
meta(.property("og:url"), .content("https://erikb.dev"))
49+
meta(.property("og:image"), .content(assets.assets.og.cardPng.url.assetString))
50+
meta(.property("og:site_name"), .content(document.title))
51+
meta(.property("og:type"), .content("website"))
52+
53+
// X (formely Twitter) meta
54+
meta(.property("twitter:card"), .content(assets.assets.og.cardPng.url.assetString))
55+
meta(.property("twitter:title"), .content(document.title))
56+
meta(.property("twitter:creator"), .content("@erikbautista_"))
57+
meta(.property("twitter:description"), .content(document.desc))
58+
59+
link(.rel("icon"), .custom(name: "type", value: "image/png"), .custom(name: "sizes", value: "16x16"), .href(assets.assets.favicon16x16Png.url.assetString))
60+
link(.rel("icon"), .custom(name: "type", value: "image/png"), .custom(name: "sizes", value: "32x32"), .href(assets.assets.favicon32x32Png.url.assetString))
61+
link(.rel("icon"), .custom(name: "type", value: "image/png"), .custom(name: "sizes", value: "96x96"), .href(assets.assets.favicon96x96Png.url.assetString))
62+
link(.rel("icon"), .custom(name: "type", value: "image/png"), .custom(name: "sizes", value: "128x128"), .href(assets.assets.favicon128x128Png.url.assetString))
63+
link(.rel("icon"), .custom(name: "type", value: "image/png"), .custom(name: "sizes", value: "196x196"), .href(assets.assets.favicon196x196Png.url.assetString))
64+
3365
style {
3466
"/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */*,::after,::before{box-sizing:border-box}html{font-family:system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji';line-height:1.15;-webkit-text-size-adjust:100%;tab-size:4}body{margin:0}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentcolor}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}"
3567
}
@@ -112,9 +144,9 @@ extension Page {
112144
body: {
113145
document.body
114146
.inlineStyle("font-optical-sizing", "auto")
115-
.inlineStyle("font-size", "0.7em")
116-
.inlineStyle("font-size", "0.8em", media: .minWidth(390))
117-
.inlineStyle("font-size", "0.9em", media: .minWidth(480))
147+
.inlineStyle("font-size", "0.78em")
148+
.inlineStyle("font-size", "0.86em", media: .minWidth(390))
149+
.inlineStyle("font-size", "0.94em", media: .minWidth(480))
118150
.attribute("lang", value: document.lang)
119151

120152
script(.type(.module), .defer) {
Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,67 @@
1+
import Dependencies
12
import HTML
23
import Models
3-
import Dependencies
44
import Vue
55

6-
struct SectionView<Content: HTML>: HTML {
6+
struct SectionView<Header: HTML, Content: HTML>: HTML {
77
let id: String
8-
let selected: Vue.Expression<CodeLang>
9-
let codeHeader: @Sendable (CodeLang) -> String
8+
let selected: Vue.Expression<CodeLang?>
9+
@HTMLBuilder let header: @Sendable (CodeLang?) -> Header
1010
@HTMLBuilder let content: @Sendable () -> Content
1111

1212
var body: some HTML {
1313
section(.id(self.id)) {
1414
div {
15-
header {
16-
pre {
17-
a(.href("#\(self.id)")) {
18-
CodeLang.conditionalCases(initial: selected) { lang in
19-
code {
20-
slugToFileName(lang)
15+
tag("header") {
16+
hgroup {
17+
pre {
18+
a(.href("#\(self.id)")) {
19+
CodeLang.conditionalCases(initial: selected) { lang in
20+
code {
21+
slugToFileName(lang)
22+
}
2123
}
2224
}
25+
.inlineStyle("color", "#777")
2326
}
24-
.inlineStyle("color", "#777")
25-
}
26-
.inlineStyle("font-size", "0.75em")
27-
.inlineStyle("font-weight", "500")
28-
.inlineStyle("text-align", "end")
29-
.inlineStyle("padding", "1.5rem 1.5rem 0")
30-
pre {
27+
.inlineStyle("font-size", "0.75em")
28+
.inlineStyle("font-weight", "500")
29+
.inlineStyle("text-align", "end")
30+
.inlineStyle("padding-bottom", "0.5rem")
31+
3132
CodeLang.conditionalCases(initial: selected) { lang in
32-
code(.class("hljs language-\(lang.rawValue)")) {
33-
"""
34-
// \(slugToFileName(lang))
35-
// Portfolio
36-
\(codeHeader(lang))
37-
"""
33+
if let lang {
34+
pre {
35+
code(.class("hljs language-\(lang.rawValue)")) {
36+
"""
37+
// \(slugToFileName(lang))
38+
// Portfolio\n
39+
"""
40+
self.header(lang)
41+
}
42+
}
43+
} else {
44+
hgroup {
45+
self.header(nil)
46+
}
3847
}
3948
}
49+
.inlineStyle("padding-bottom", "0.75rem")
4050
}
41-
.inlineStyle("padding", "0.75rem 1.5rem 1.5rem")
4251
}
52+
.inlineStyle("padding", "1.5rem")
53+
4354
self.content()
4455
}
4556
.containerStyling()
4657
}
4758
.wrappedStyling()
4859
}
4960

50-
private func slugToFileName(_ lang: CodeLang) -> String {
51-
let fileName = switch lang {
61+
private func slugToFileName(_ lang: CodeLang?) -> String {
62+
let fileName =
63+
switch lang {
64+
case .none: self.id
5265
case .swift:
5366
self.id.components(separatedBy: "-")
5467
.map { component -> String in
@@ -74,12 +87,12 @@ struct SectionView<Content: HTML>: HTML {
7487
}
7588
.joined()
7689
}
77-
return fileName + "." + lang.ext
90+
return fileName + "." + (lang?.ext ?? "md")
7891
}
7992
}
8093

8194
extension [CodeLang] {
8295
func sorted(initial lang: CodeLang) -> Self {
8396
[lang] + self.filter { $0 != lang }
8497
}
85-
}
98+
}

0 commit comments

Comments
 (0)