Skip to content

Commit 68e5883

Browse files
added htmx websocket stuff; some htmx fixes
1 parent 751f52c commit 68e5883

File tree

3 files changed

+188
-12
lines changed

3 files changed

+188
-12
lines changed

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,19 @@ private extension HTMLElement {
181181
key = "aria-" + first_expression.functionCall!.calledExpression.memberAccess!.declName.baseName.text
182182
}
183183
if key == "htmx" {
184-
let target:String = first_expression.functionCall!.calledExpression.memberAccess!.declName.baseName.text
185184
var string:String = "\(first_expression)"
186185
string = String(string[string.index(after: string.startIndex)...])
187186
if let htmx:HTMLElementAttribute.HTMX = HTMLElementAttribute.HTMX(rawValue: string) {
187+
switch htmx {
188+
case .ws(let value):
189+
key = "ws-" + value.key
190+
break
191+
default:
192+
key = "hx-" + htmx.key
193+
break
194+
}
188195
let htmlValue:String = htmx.htmlValue
189-
value = "hx-" + target + (htmlValue.isEmpty ? "" : "=\\\"" + htmlValue + "\\\"")
196+
value = key + (htmlValue.isEmpty ? "" : "=\\\"" + htmlValue + "\\\"")
190197
}
191198
} else if let string:String = parse_attribute(context: context, elementType: elementType, key: key, expression: first_expression, lookupFiles: lookupFiles) {
192199
value = string

Sources/HTMLKitUtilities/HTMX.swift

Lines changed: 153 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,15 @@ public extension HTMLElementAttribute {
4343
case trigger(String)
4444
case vals(String)
4545

46+
case ws(WebSocket)
47+
4648
public init?(rawValue: String) {
4749
guard rawValue.last == ")" else { return nil }
4850
let start:String.Index = rawValue.startIndex, end:String.Index = rawValue.index(before: rawValue.endIndex), end_minus_one:String.Index = rawValue.index(before: end)
4951
let key:Substring = rawValue.split(separator: "(")[0]
52+
func literal() -> String {
53+
return String(rawValue[rawValue.index(start, offsetBy: key.count + 2)..<end])
54+
}
5055
func string() -> String {
5156
return String(rawValue[rawValue.index(start, offsetBy: key.count + 2)..<end_minus_one])
5257
}
@@ -65,37 +70,92 @@ public extension HTMLElementAttribute {
6570
case "disinherit": self = .disinherit(string())
6671
case "encoding": self = .encoding(string())
6772
case "ext": self = .ext(string())
68-
//case "headers": self = .headers(js: Bool, [String : String])
73+
//case "headers": self = .headers(js: Bool, [String : String]) // TODO: fix
6974
case "history": self = .history(enumeration())
7075
case "historyElt": self = .historyElt(boolean())
7176
case "include": self = .include(string())
7277
case "indicator": self = .indicator(string())
7378
case "inherit": self = .inherit(string())
74-
//case "params": self = .params(enumeration())
79+
case "params": self = .params(Params(rawValue: literal())!)
7580
case "patch": self = .patch(string())
7681
case "preserve": self = .preserve(boolean())
7782
case "prompt": self = .prompt(string())
7883
case "put": self = .put(string())
79-
//case "replaceURL": self = .replaceURL(enumeration())
80-
//case "request": self = .request(js: Bool, timeout: Int, credentials: Bool, noHeaders: Bool)
81-
//case "sync": self = .sync(String, strategy: SyncStrategy?)
84+
case "replaceURL": self = .replaceURL(URL(rawValue: literal())!)
85+
//case "request": self = .request(js: Bool, timeout: Int, credentials: Bool, noHeaders: Bool) // TODO: fix
86+
//case "sync": self = .sync(String, strategy: SyncStrategy?) // TODO: fix
8287
case "validate": self = .validate(enumeration())
8388

8489
case "get": self = .get(string())
8590
case "post": self = .post(string())
86-
//case "on": self = .on(Event, String)
87-
//case "pushURL": self = .pushURL(enumeration())
91+
case "on":
92+
let string:String = literal()
93+
let values:[Substring] = string.split(separator: ",")
94+
let event_string:String = String(values[0])
95+
var value:String = String(string[values[1].startIndex...])
96+
while (value.first?.isWhitespace ?? false) || value.first == "\"" {
97+
value.removeFirst()
98+
}
99+
value.removeLast()
100+
let event:Event = Event(rawValue: event_string)!
101+
self = .on(event, value)
102+
break
103+
case "pushURL": self = .pushURL(URL(rawValue: string())!)
88104
case "select": self = .select(string())
89105
case "selectOOB": self = .selectOOB(string())
90106
case "swap": self = .swap(enumeration())
91107
case "swapOOB": self = .swapOOB(string())
92108
case "target": self = .target(string())
93109
case "trigger": self = .trigger(string())
94110
case "vals": self = .vals(string())
111+
112+
case "ws": self = .ws(WebSocket(rawValue: literal())!)
95113
default: return nil
96114
}
97115
}
98116

117+
public var key : String {
118+
switch self {
119+
case .boost(_): return "boost"
120+
case .confirm(_): return "confirm"
121+
case .delete(_): return "delete"
122+
case .disable(_): return "disable"
123+
case .disabledElt(_): return "disable-elt"
124+
case .disinherit(_): return "disinherit"
125+
case .encoding(_): return "encoding"
126+
case .ext(_): return "ext"
127+
case .headers(_, _): return "headers"
128+
case .history(_): return "history"
129+
case .historyElt(_): return "historyElt"
130+
case .include(_): return "include"
131+
case .indicator(_): return "indicator"
132+
case .inherit(_): return "inherit"
133+
case .params(_): return "params"
134+
case .patch(_): return "patch"
135+
case .preserve(_): return "preserve"
136+
case .prompt(_): return "prompt"
137+
case .put(_): return "put"
138+
case .replaceURL(_): return "replace-url"
139+
case .request(_, _, _, _): return "request"
140+
case .sync(_, _): return "sync"
141+
case .validate(_): return "validate"
142+
143+
case .get(_): return "get"
144+
case .post(_): return "post"
145+
case .on(let event, _): return "on:" + event.rawValue
146+
case .pushURL(_): return "push-url"
147+
case .select(_): return "select"
148+
case .selectOOB(_): return "select-oob"
149+
case .swap(_): return "swap"
150+
case .swapOOB(_): return "swap-oob"
151+
case .target(_): return "target"
152+
case .trigger(_): return "trigger"
153+
case .vals(_): return "vals"
154+
155+
case .ws(let value): return "ws-" + value.key
156+
}
157+
}
158+
99159
public var htmlValue : String {
100160
switch self {
101161
case .boost(let value): return value.rawValue
@@ -106,8 +166,8 @@ public extension HTMLElementAttribute {
106166
case .disinherit(let value): return value
107167
case .encoding(let value): return value
108168
case .ext(let value): return value
109-
case .headers(let js, let headers):
110-
return js ? "" : headers.map({ "\"" + $0.key + "\":\"" + $0.value + "\"" }).joined(separator: ",")
169+
case .headers(let js, let headers): // TODO: fix
170+
return js ? "" : "{" + headers.map({ "\\\"" + $0.key + "\\\":\\\"" + $0.value + "\\\"" }).joined(separator: ",") + "}"
111171
case .history(let value): return value.rawValue
112172
case .historyElt(_): return ""
113173
case .include(let value): return value
@@ -120,7 +180,7 @@ public extension HTMLElementAttribute {
120180
case .put(let value): return value
121181
case .replaceURL(let url): return url.htmlValue
122182
case .request(let js, let timeout, let credentials, let noHeaders):
123-
return ""
183+
return "" // TODO: fix
124184
case .sync(let selector, let strategy):
125185
return selector + (strategy == nil ? "" : ":" + strategy!.htmlValue)
126186
case .validate(let value): return value.rawValue
@@ -136,6 +196,8 @@ public extension HTMLElementAttribute {
136196
case .target(let value): return value
137197
case .trigger(let value): return value
138198
case .vals(let value): return value
199+
200+
case .ws(let value): return value.htmlValue
139201
}
140202
}
141203
}
@@ -223,6 +285,25 @@ public extension HTMLElementAttribute.HTMX {
223285
case not([String])
224286
case list([String])
225287

288+
public init?(rawValue: String) {
289+
let key:Substring = rawValue.split(separator: "(")[0]
290+
func array_string() -> [String] {
291+
let string:String = String(rawValue[rawValue.index(rawValue.startIndex, offsetBy: key.count + 2)..<rawValue.index(before: rawValue.endIndex)])
292+
let ranges:[Range<String.Index>] = try! string.ranges(of: Regex("\"([^\"]+)\"")) // TODO: fix? (doesn't parse correctly if the string contains escaped quotation marks)
293+
return ranges.map({
294+
let item:String = String(string[$0])
295+
return String(item[item.index(after: item.startIndex)..<item.index(before: item.endIndex)])
296+
})
297+
}
298+
switch key {
299+
case "all": self = .all
300+
case "none": self = .none
301+
case "not": self = .not(array_string())
302+
case "list": self = .list(array_string())
303+
default: return nil
304+
}
305+
}
306+
226307
public var htmlValue : String {
227308
switch self {
228309
case .all: return "*"
@@ -266,6 +347,20 @@ public extension HTMLElementAttribute.HTMX {
266347
case `true`, `false`
267348
case url(String)
268349

350+
public init?(rawValue: String) {
351+
let key:Substring = rawValue.split(separator: "(")[0]
352+
let end:String.Index = rawValue.index(before: rawValue.endIndex), end_minus_one:String.Index = rawValue.index(before: end)
353+
func string() -> String {
354+
return String(rawValue[rawValue.index(rawValue.startIndex, offsetBy: key.count + 2)..<end_minus_one])
355+
}
356+
switch key {
357+
case "true": self = .true
358+
case "false": self = .false
359+
case "url": self = .url(string())
360+
default: return nil
361+
}
362+
}
363+
269364
public var htmlValue : String {
270365
switch self {
271366
case .true: return "true"
@@ -274,4 +369,52 @@ public extension HTMLElementAttribute.HTMX {
274369
}
275370
}
276371
}
372+
}
373+
374+
// MARK: WebSocket
375+
public extension HTMLElementAttribute.HTMX {
376+
enum WebSocket {
377+
case connect(String)
378+
case send(String)
379+
380+
public init?(rawValue: String) {
381+
guard rawValue.last == ")" else { return nil }
382+
let start:String.Index = rawValue.startIndex, end:String.Index = rawValue.index(before: rawValue.endIndex), end_minus_one:String.Index = rawValue.index(before: end)
383+
let key:Substring = rawValue.split(separator: "(")[0]
384+
func string() -> String {
385+
return String(rawValue[rawValue.index(start, offsetBy: key.count + 2)..<end_minus_one])
386+
}
387+
switch key {
388+
case "connect": self = .connect(string())
389+
case "send": self = .send(string())
390+
default: return nil
391+
}
392+
}
393+
394+
public var key : String {
395+
switch self {
396+
case .connect(_): return "connect"
397+
case .send(_): return "send"
398+
}
399+
}
400+
401+
public var htmlValue : String {
402+
switch self {
403+
case .connect(let value): return value
404+
case .send(let value): return value
405+
}
406+
}
407+
408+
public enum Event : String {
409+
case wsConnecting
410+
case wsOpen
411+
case wsClose
412+
case wsError
413+
case wsBeforeMessage
414+
case wsAfterMessage
415+
case wsConfigSend
416+
case wsBeforeSend
417+
case wsAfterSend
418+
}
419+
}
277420
}

Tests/HTMLKitTests/HTMXTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,39 @@ import Testing
99
import HTMLKit
1010

1111
struct HTMXTests {
12+
// MARK: get
1213
@Test func get() {
1314
let string:StaticString = #div(attributes: [.htmx(.get("/test"))])
1415
#expect(string == "<div hx-get=\"/test\"></div>")
1516
}
1617

18+
// MARK: on
19+
@Test func on() {
20+
var string:StaticString = #div(attributes: [.htmx(.on(.abort, "bruh"))])
21+
#expect(string == "<div hx-on:abort=\"bruh\"></div>")
22+
}
23+
24+
// MARK: post
1725
@Test func post() {
1826
let string:StaticString = #div(attributes: [.htmx(.post("https://github.com/RandomHashTags"))])
1927
#expect(string == "<div hx-post=\"https://github.com/RandomHashTags\"></div>")
2028
}
29+
30+
// MARK: replaceURL
31+
@Test func replaceURL() {
32+
var string:StaticString = #div(attributes: [.htmx(.replaceURL(.true))])
33+
#expect(string == "<div hx-replace-url=\"true\"></div>")
34+
35+
string = #div(attributes: [.htmx(.replaceURL(.url("https://litleagues.com")))])
36+
#expect(string == "<div hx-replace-url=\"https://litleagues.com\"></div>")
37+
}
38+
39+
// MARK: ws
40+
@Test func ws() {
41+
var string:StaticString = #div(attributes: [.htmx(.ws(.connect("https://paradigm-app.com")))])
42+
#expect(string == "<div ws-connect=\"https://paradigm-app.com\"></div>")
43+
44+
string = #div(attributes: [.htmx(.ext("ws")), .htmx(.ws(.send("https://linktr.ee/anderson_evan")))])
45+
#expect(string == "<div hx-ext=\"ws\" ws-send=\"https://linktr.ee/anderson_evan\"></div>")
46+
}
2147
}

0 commit comments

Comments
 (0)