Skip to content

Commit 9479398

Browse files
fixed headers & sync HTMX attribute behaviors
- all htmx attributes should now be supported
1 parent 9b765fa commit 9479398

File tree

3 files changed

+105
-8
lines changed

3 files changed

+105
-8
lines changed

Sources/HTMLKitMacros/HTMLElement.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private extension HTMLElement {
188188
let htmlValue:String = htmx.htmlValue
189189
var delimiter:String = "\\\""
190190
switch htmx {
191-
case .request(_, _, _, _):
191+
case .request(_, _, _, _), .headers(_, _):
192192
delimiter = "'"
193193
break
194194
case .ws(let value):

Sources/HTMLKitUtilities/HTMX.swift

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,27 @@ public extension HTMLElementAttribute {
7171
case "disinherit": self = .disinherit(string())
7272
case "encoding": self = .encoding(string())
7373
case "ext": self = .ext(string())
74-
//case "headers": self = .headers(js: Bool, [String : String]) // TODO: fix
74+
case "headers":
75+
let values:[Substring] = rawValue.split(separator: ",")
76+
let js:Bool = values[0].hasSuffix("true")
77+
var headers_string:Substring = rawValue[rawValue.firstIndex(of: "[")!...]
78+
headers_string.removeLast() // )
79+
let regex:Regex = try! Regex("(\"[^\"]+\")")
80+
let matches:[Range<Substring.Index>] = headers_string.ranges(of: regex)
81+
var headers:[String:String] = [:]
82+
var header_key:Substring = ""
83+
for i in 0..<matches.count {
84+
var value:Substring = headers_string[matches[i]]
85+
value.removeFirst() // "
86+
value.removeLast() // "
87+
if i % 2 == 0 {
88+
header_key = value
89+
} else {
90+
headers[String(header_key)] = String(value)
91+
}
92+
}
93+
self = .headers(js: js, headers)
94+
break
7595
case "history": self = .history(enumeration())
7696
case "historyElt": self = .historyElt(boolean())
7797
case "include": self = .include(string())
@@ -117,7 +137,21 @@ public extension HTMLElementAttribute {
117137
}
118138
self = .request(js: javascript, timeout: timeout, credentials: credentials, noHeaders: noHeaders)
119139
break
120-
//case "sync": self = .sync(String, strategy: SyncStrategy?) // TODO: fix
140+
case "sync":
141+
let string:String = literal()
142+
let values:[Substring] = string.split(separator: ",")
143+
var key:Substring = values[0]
144+
key.removeLast() // "
145+
var strategy:SyncStrategy? = nil
146+
var strategy_string:Substring = values[1].split(separator: ":")[1]
147+
if !strategy_string.hasSuffix("nil") {
148+
while (strategy_string.first?.isWhitespace ?? false) || strategy_string.first == "." {
149+
strategy_string.removeFirst()
150+
}
151+
strategy = SyncStrategy(rawValue: String(strategy_string))
152+
}
153+
self = .sync(String(key), strategy: strategy)
154+
break
121155
case "validate": self = .validate(enumeration())
122156

123157
case "get": self = .get(string())
@@ -364,10 +398,6 @@ public extension HTMLElementAttribute.HTMX {
364398
}
365399
}
366400

367-
// MARK: Modifiers
368-
enum Modifier {
369-
}
370-
371401
// MARK: Params
372402
enum Params {
373403
case all
@@ -418,6 +448,21 @@ public extension HTMLElementAttribute.HTMX {
418448
case drop, abort, replace
419449
case queue(Queue)
420450

451+
public init?(rawValue: String) {
452+
let values:[Substring] = rawValue.split(separator: "(")
453+
switch values[0] {
454+
case "drop": self = .drop
455+
case "abort": self = .abort
456+
case "replace": self = .replace
457+
case "queue":
458+
guard let value_index:Substring.Index = values[1].firstIndex(where: { $0.isLetter }) else { return nil }
459+
let value:Substring = rawValue[value_index..<values[1].index(before: values[1].endIndex)]
460+
self = .queue(Queue(rawValue: String(value))!)
461+
break
462+
default: return nil
463+
}
464+
}
465+
421466
public enum Queue : String {
422467
case first, last, all
423468
}
@@ -427,7 +472,7 @@ public extension HTMLElementAttribute.HTMX {
427472
case .drop: return "drop"
428473
case .abort: return "abort"
429474
case .replace: return "replace"
430-
case .queue(let queue): return queue.rawValue
475+
case .queue(let queue): return "queue " + queue.rawValue
431476
}
432477
}
433478
}

Tests/HTMLKitTests/HTMXTests.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,49 @@ struct HTMXTests {
2323
#expect(string == "<div hx-get=\"/test\"></div>")
2424
}
2525

26+
// MARK: headers
27+
@Test func headers() {
28+
let set:Set<String> = dictionary_json_results(tag: "div", closingTag: true, attribute: "hx-headers", delimiter: "'", ["womp":"womp", "ding dong":"d1tched", "EASY":"C,L.a;P!"])
29+
let string:StaticString = #div(attributes: [.htmx(.headers(js: false, ["womp":"womp", "ding dong":"d1tched", "EASY":"C,L.a;P!"]))])
30+
#expect(set.contains(string.description), Comment(rawValue: "string=\(string)\nset=\(set)"))
31+
}
32+
func dictionary_json_results(
33+
tag: String,
34+
closingTag: Bool,
35+
attribute: String,
36+
delimiter: String,
37+
_ dictionary: [String:String]
38+
) -> Set<String> { // TODO: fix (doesn't check all available values)
39+
var set:Set<String> = []
40+
let prefix:String = "<" + tag + " " + attribute + "=" + delimiter
41+
let suffix:String = delimiter + ">" + (closingTag ? "</" + tag + ">" : "")
42+
set.reserveCapacity(dictionary.count*dictionary.count)
43+
for (key1, value1) in dictionary {
44+
let string:String = prefix + "{\"" + key1 + "\":\"" + value1 + "\","
45+
46+
var string1:String = string
47+
for (key2, value2) in dictionary {
48+
if key1 != key2 {
49+
string1 += "\"" + key2 + "\":\"" + value2 + "\","
50+
}
51+
}
52+
string1.removeLast()
53+
string1 += "}" + suffix
54+
set.insert(string1)
55+
56+
var string2:String = string
57+
for (key2, value2) in dictionary.reversed() {
58+
if key1 != key2 {
59+
string2 += "\"" + key2 + "\":\"" + value2 + "\","
60+
}
61+
}
62+
string2.removeLast()
63+
string2 += "}" + suffix
64+
set.insert(string2)
65+
}
66+
return set
67+
}
68+
2669
// MARK: on
2770
@Test func on() {
2871
var string:StaticString = #div(attributes: [.htmx(.on(.abort, "bruh"))])
@@ -71,6 +114,15 @@ struct HTMXTests {
71114
#expect(string == "<div hx-request='{\"noHeaders\":true}'></div>")
72115
}
73116

117+
// MARK: sync
118+
@Test func sync() {
119+
var string:StaticString = #div(attributes: [.htmx(.sync("closest form", strategy: .abort))])
120+
#expect(string == "<div hx-sync=\"closest form:abort\"></div>")
121+
122+
string = #div(attributes: [.htmx(.sync("#submit-button", strategy: .queue(.first)))])
123+
#expect(string == "<div hx-sync=\"#submit-button:queue first\"></div>")
124+
}
125+
74126
// MARK: ws
75127
@Test func ws() {
76128
var string:StaticString = #div(attributes: [.htmx(.ws(.connect("https://paradigm-app.com")))])

0 commit comments

Comments
 (0)