From a957fdf1052b21484ba5f905342119a039454d1a Mon Sep 17 00:00:00 2001 From: Hal Lee Date: Sat, 11 Apr 2020 15:49:38 -0400 Subject: [PATCH 01/33] Add viewport-fit modes --- Sources/Plot/API/HTMLComponents.swift | 8 ++++++-- Sources/Plot/API/HTMLViewportFitMode.swift | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 Sources/Plot/API/HTMLViewportFitMode.swift diff --git a/Sources/Plot/API/HTMLComponents.swift b/Sources/Plot/API/HTMLComponents.swift index 903a4a5..05f37a9 100644 --- a/Sources/Plot/API/HTMLComponents.swift +++ b/Sources/Plot/API/HTMLComponents.swift @@ -87,8 +87,12 @@ public extension Node where Context == HTML.HeadContext { /// to the device the page is being rendered on. See `HTMLViewportWidthMode`. /// - parameter initialScale: The initial scale that the page should use. static func viewport(_ widthMode: HTMLViewportWidthMode, - initialScale: Double = 1) -> Node { - let content = "width=\(widthMode.string), initial-scale=\(initialScale)" + initialScale: Double = 1, + viewportFitMode: HTMLViewportFitMode? = nil) -> Node { + var content = "width=\(widthMode.string), initial-scale=\(initialScale)" + if let viewportFitMode = viewportFitMode { + content += ", viewport-fit=\(viewportFitMode.rawValue)" + } return .meta(.name("viewport"), .content(content)) } diff --git a/Sources/Plot/API/HTMLViewportFitMode.swift b/Sources/Plot/API/HTMLViewportFitMode.swift new file mode 100644 index 0000000..8ea81f3 --- /dev/null +++ b/Sources/Plot/API/HTMLViewportFitMode.swift @@ -0,0 +1,22 @@ +/** +* Plot +* Copyright (c) John Sundell 2019 +* MIT license, see LICENSE file for details +*/ + +import Foundation + +/// Enum defining the fit parameters of the viewport meta tag. +public enum HTMLViewportFitMode: String { + /// This value doesn’t affect the initial layout viewport, and the whole web page is viewable. + /// What the UA paints outside of the viewport is undefined. + /// It may be the background color of the canvas, or anything else that the UA deems appropriate. + case auto + /// The initial layout viewport and the visual viewport are set to the largest rectangle which is + /// inscribed in the display of the device. What the UA paints outside of the viewport is undefined. + /// It may be the background color of the canvas, or anything else that the UA deems appropriate. + case contain + /// The initial layout viewport and the visual viewport + /// are set to the circumscribed rectangle of the physical screen of the device. + case cover +} From 44825560a906ebf9cbf10c1646d30cb014a2559d Mon Sep 17 00:00:00 2001 From: Hal Lee Date: Sat, 11 Apr 2020 15:52:41 -0400 Subject: [PATCH 02/33] Cleaner API --- Sources/Plot/API/HTMLComponents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Plot/API/HTMLComponents.swift b/Sources/Plot/API/HTMLComponents.swift index 05f37a9..9518043 100644 --- a/Sources/Plot/API/HTMLComponents.swift +++ b/Sources/Plot/API/HTMLComponents.swift @@ -88,7 +88,7 @@ public extension Node where Context == HTML.HeadContext { /// - parameter initialScale: The initial scale that the page should use. static func viewport(_ widthMode: HTMLViewportWidthMode, initialScale: Double = 1, - viewportFitMode: HTMLViewportFitMode? = nil) -> Node { + viewportFit: HTMLViewportFitMode? = nil) -> Node { var content = "width=\(widthMode.string), initial-scale=\(initialScale)" if let viewportFitMode = viewportFitMode { content += ", viewport-fit=\(viewportFitMode.rawValue)" From 11b06f66832a34a6d94f534659f221a1c3bc1074 Mon Sep 17 00:00:00 2001 From: Hal Lee Date: Sat, 11 Apr 2020 15:55:56 -0400 Subject: [PATCH 03/33] Even cleaner --- Sources/Plot/API/HTMLComponents.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Plot/API/HTMLComponents.swift b/Sources/Plot/API/HTMLComponents.swift index 9518043..d30bc5e 100644 --- a/Sources/Plot/API/HTMLComponents.swift +++ b/Sources/Plot/API/HTMLComponents.swift @@ -88,10 +88,10 @@ public extension Node where Context == HTML.HeadContext { /// - parameter initialScale: The initial scale that the page should use. static func viewport(_ widthMode: HTMLViewportWidthMode, initialScale: Double = 1, - viewportFit: HTMLViewportFitMode? = nil) -> Node { + fit: HTMLViewportFitMode? = nil) -> Node { var content = "width=\(widthMode.string), initial-scale=\(initialScale)" - if let viewportFitMode = viewportFitMode { - content += ", viewport-fit=\(viewportFitMode.rawValue)" + if let fit = fit { + content += ", viewport-fit=\(fit.rawValue)" } return .meta(.name("viewport"), .content(content)) } From d796124cd17da541d9aae0009f36ffbb9a5bc683 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 15 Jun 2020 08:56:22 -0500 Subject: [PATCH 04/33] Add label attribute to option elements (#50) --- Sources/Plot/API/HTMLAttributes.swift | 6 ++++++ Tests/PlotTests/HTMLTests.swift | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/Plot/API/HTMLAttributes.swift b/Sources/Plot/API/HTMLAttributes.swift index 2ee914d..22898b5 100644 --- a/Sources/Plot/API/HTMLAttributes.swift +++ b/Sources/Plot/API/HTMLAttributes.swift @@ -335,6 +335,12 @@ public extension Attribute where Context == HTML.OptionContext { ignoreIfValueIsEmpty: false ) } + + /// Assign a label to the given option. + /// - parameter label: The user displayed value of the option + static func label(_ label: String) -> Attribute { + Attribute(name: "label", value: label, ignoreIfValueIsEmpty: false) + } } // MARK: - Layout and styling diff --git a/Tests/PlotTests/HTMLTests.swift b/Tests/PlotTests/HTMLTests.swift index ccac2b9..183a12c 100644 --- a/Tests/PlotTests/HTMLTests.swift +++ b/Tests/PlotTests/HTMLTests.swift @@ -585,14 +585,14 @@ final class HTMLTests: XCTestCase { ), .select( .option(.value("C"), .isSelected(true)), - .option(.value("D"), .isSelected(false)) + .option(.value("D"), .label("Dee"), .isSelected(false)) ) )) assertEqualHTMLContent(html, """ \ \ - \ + \ """) } From 3e0d8363975cb9d29e2cb0eed72a381934c92052 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 15 Jun 2020 08:57:26 -0500 Subject: [PATCH 05/33] Add table grouping semantic html (#49) , , and --- Sources/Plot/API/HTMLElements.swift | 18 +++++++++++++ Tests/PlotTests/HTMLTests.swift | 41 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/Sources/Plot/API/HTMLElements.swift b/Sources/Plot/API/HTMLElements.swift index c63aa5e..2c3a910 100644 --- a/Sources/Plot/API/HTMLElements.swift +++ b/Sources/Plot/API/HTMLElements.swift @@ -439,6 +439,24 @@ public extension Node where Context == HTML.TableContext { static func tr(_ nodes: Node...) -> Node { .element(named: "tr", nodes: nodes) } + + /// Add a `` HTML element within the current context. + /// - parameter nodes: The element's attributes and child elements. + static func thead(_ nodes: Node...) -> Node { + .element(named: "thead", nodes: nodes) + } + + /// Add a `` HTML element within the current context. + /// - parameter nodes: The element's attributes and child elements. + static func tbody(_ nodes: Node...) -> Node { + .element(named: "tbody", nodes: nodes) + } + + /// Add a `` HTML element within the current context. + /// - parameter nodes: The element's attributes and child elements. + static func tfoot(_ nodes: Node...) -> Node { + .element(named: "tfoot", nodes: nodes) + } } public extension Node where Context == HTML.TableRowContext { diff --git a/Tests/PlotTests/HTMLTests.swift b/Tests/PlotTests/HTMLTests.swift index 183a12c..33673ca 100644 --- a/Tests/PlotTests/HTMLTests.swift +++ b/Tests/PlotTests/HTMLTests.swift @@ -263,6 +263,46 @@ final class HTMLTests: XCTestCase { """) } + func testTableGroupingSemantics() { + let html = HTML( + .body( + .table( + .thead( + .tr( + .th("Column1"), + .th("Column2") + ) + ), + .tbody( + .tr( + .td("Body1"), + .td("Body2") + ), + .tr( + .td("Body3"), + .td("Body4") + ) + ), + .tfoot( + .tr( + .td("Foot1"), + .td("Foot2") + ) + ) + ) + ) + ) + + assertEqualHTMLContent(html, """ + \ + \ + \ + \ + \ +
Column1Column2
Body1Body2
Body3Body4
Foot1Foot2
+ """) + } + func testData() { let html = HTML(.body( .data(.value("123"), .text("Hello")) @@ -764,6 +804,7 @@ extension HTMLTests { ("testDescriptionList", testDescriptionList), ("testAnchors", testAnchors), ("testTable", testTable), + ("testTableGroupingSemantics", testTableGroupingSemantics), ("testData", testData), ("testEmbeddedObject", testEmbeddedObject), ("testForm", testForm), From 4d6eeb2a9982b6cb945ab7c89cc6039d921efbad Mon Sep 17 00:00:00 2001 From: CMK Date: Fri, 11 Sep 2020 22:52:03 +0800 Subject: [PATCH 06/33] feat: make img tag supports width and height attribute (#55) --- Sources/Plot/API/HTML.swift | 2 +- Tests/PlotTests/HTMLTests.swift | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/Plot/API/HTML.swift b/Sources/Plot/API/HTML.swift index 49737a4..d9ce069 100644 --- a/Sources/Plot/API/HTML.swift +++ b/Sources/Plot/API/HTML.swift @@ -68,7 +68,7 @@ public extension HTML { /// The context within an HTML ` + """) + } + + func testImageWithDescription() { + let html = Image(url: "image.png", description: "My image").render() + XCTAssertEqual(html, #"My image"#) + } + + func testImageWithoutDescription() { + let html = Image("image.png").render() + XCTAssertEqual(html, #""#) + } + + func testLinkRelationshipAndTarget() { + let html = Div { + Link("First", url: "/first") + Link("Second", url: "/second") + .linkRelationship(.noreferrer) + .linkTarget(nil) + } + .linkRelationship(.nofollow) + .linkTarget(.blank) + .render() + + XCTAssertEqual(html, """ +
\ + First\ + Second\ +
+ """) + } + + func testOrderedList() { + let html = List(["One", "Two"]) + .listStyle(.ordered) + .render() + + XCTAssertEqual(html, "
  1. One
  2. Two
") + } + + func testOrderedListWithExplicitItems() { + struct SeventhComponent: Component { + var body: Component { ListItem("Seven") } + } + + let bool = true + + let html = List { + ListItem("One").number(1) + Text("Two") + + if bool { + Paragraph("Three").class("three") + } + + ListItem("Four").class("four") + + for string in ["Five", "Six"] { + ListItem(string) + } + + SeventhComponent() + + Node.li("Eight") + + Node.group( + .li("Nine"), + .li("Ten", .class("ten")) + ) + } + .listStyle(.ordered) + .render() + + XCTAssertEqual(html, """ +
    \ +
  1. One
  2. \ +
  3. Two
  4. \ +
  5. Three

  6. \ +
  7. Four
  8. \ +
  9. Five
  10. \ +
  11. Six
  12. \ +
  13. Seven
  14. \ +
  15. Eight
  16. \ +
  17. Nine
  18. \ +
  19. Ten
  20. \ +
+ """) + } + + func testOrderedListWithEmptyComponent() { + let html = List { + Text("Hello") + EmptyComponent() + } + .listStyle(.ordered) + .render() + + XCTAssertEqual(html, "
  1. Hello
") + } + + func testUnorderedList() { + let html = List(["One", "Two"]).render() + XCTAssertEqual(html, "
  • One
  • Two
") + } + + func testUnorderedListWithCustomItemClass() { + let html = List([1, 2]) { number in + Paragraph(String(number)) + } + .listStyle(.unordered.withItemClass("item")) + .render() + + XCTAssertEqual(html, """ +
    \ +
  • 1

  • \ +
  • 2

  • \ +
+ """) + } + + func testUngroupedTable() { + let html = Table { + Text("Row one") + TableRow { + TableCell("Row two, cell one") + TableCell("Row two, cell two") + } + + ComponentGroup { + TableRow { + Text("Row three, cell one") + Text("Row three, cell two") + } + TableCell("Row four") + } + } + .render() + + XCTAssertEqual(html, """ + \ + \ + \ + \ + \ +
Row one
Row two, cell oneRow two, cell two
Row three, cell oneRow three, cell two
Row four
+ """) + } + + func testGroupedTable() { + let html = Table( + caption: TableCaption("Caption"), + header: TableRow { Text("Header") }, + footer: TableRow { Text("Footer") }, + rows: { + Text("Row one") + TableRow { + TableCell("Row two, cell one") + TableCell("Row two, cell two") + } + + ComponentGroup { + TableRow { + Text("Row three, cell one") + Text("Row three, cell two") + } + TableCell("Row four") + } + } + ) + .render() + + XCTAssertEqual(html, """ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
Caption
Header
Row one
Row two, cell oneRow two, cell two
Row three, cell oneRow three, cell two
Row four
Footer
+ """) + } +} diff --git a/Tests/PlotTests/NodeTests.swift b/Tests/PlotTests/NodeTests.swift index d8504b4..1d0eb3f 100644 --- a/Tests/PlotTests/NodeTests.swift +++ b/Tests/PlotTests/NodeTests.swift @@ -71,4 +71,18 @@ final class NodeTests: XCTestCase { XCTAssertEqual(node.render(), #""#) } + + func testComponents() { + let node = Node.components { + Paragraph("One") + Paragraph("Two") + } + + XCTAssertEqual(node.render(), "

One

Two

") + } + + func testNodeComponentBodyIsEqualToSelf() { + let node = Node.p("Text") + XCTAssertEqual(node.render(), node.body.render()) + } } From 27ba4be0fc8dfae83542011ed4fcf39d6ae26db5 Mon Sep 17 00:00:00 2001 From: Michael Wermeester Date: Tue, 11 May 2021 21:29:37 +0200 Subject: [PATCH 10/33] Fix RSS 'content' namespace. (#59) --- Sources/Plot/API/RSS.swift | 2 +- Tests/PlotTests/Assertions.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Plot/API/RSS.swift b/Sources/Plot/API/RSS.swift index 6290c68..5e935ef 100644 --- a/Sources/Plot/API/RSS.swift +++ b/Sources/Plot/API/RSS.swift @@ -87,7 +87,7 @@ internal extension Document where Format: RSSBasedDocumentFormat { .rss( .version(2.0), .namespace("atom", "http://www.w3.org/2005/Atom"), - .namespace("content", "http://purl.org/rss/1.0/modules/content"), + .namespace("content", "http://purl.org/rss/1.0/modules/content/"), .group(nodes) ) ]) diff --git a/Tests/PlotTests/Assertions.swift b/Tests/PlotTests/Assertions.swift index 44f0169..69fc01e 100644 --- a/Tests/PlotTests/Assertions.swift +++ b/Tests/PlotTests/Assertions.swift @@ -140,7 +140,7 @@ func assertEqualPodcastFeedContent( type: "podcast", namespaces: [ ("atom", "http://www.w3.org/2005/Atom"), - ("content", "http://purl.org/rss/1.0/modules/content"), + ("content", "http://purl.org/rss/1.0/modules/content/"), ("itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd"), ("media", "http://www.rssboard.org/media-rss") ], @@ -161,7 +161,7 @@ func assertEqualRSSFeedContent( type: "RSS", namespaces: [ ("atom", "http://www.w3.org/2005/Atom"), - ("content", "http://purl.org/rss/1.0/modules/content") + ("content", "http://purl.org/rss/1.0/modules/content/") ], file: file, line: line From 0ca2d6bae6ca2b6065d5c927550e44cbed1e5796 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Wed, 12 May 2021 16:11:19 +0200 Subject: [PATCH 11/33] Prevent empty attribute values from being appended (#62) When adding an attribute that should be appended to any existing one, first check if either the existing or new attribute contain an empty value, and if so, don't perform the append. Otherwise, we'll end up with extra whitespace within attribute values. Also, slight cleanup of the attribute handling logic within `ElementRenderingBuffer`, to make the conditionals a bit more clear. --- Sources/Plot/API/Attribute.swift | 2 +- Sources/Plot/Internal/AnyAttribute.swift | 6 ++++++ Sources/Plot/Internal/ElementRenderingBuffer.swift | 12 +++++++----- Tests/PlotTests/HTMLComponentTests.swift | 11 +++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Sources/Plot/API/Attribute.swift b/Sources/Plot/API/Attribute.swift index 06e91dd..932b24e 100644 --- a/Sources/Plot/API/Attribute.swift +++ b/Sources/Plot/API/Attribute.swift @@ -64,7 +64,7 @@ extension Attribute: NodeConvertible { extension Attribute: AnyAttribute { func render() -> String { - guard let value = value, !value.isEmpty else { + guard let value = nonEmptyValue else { return ignoreIfValueIsEmpty ? "" : name } diff --git a/Sources/Plot/Internal/AnyAttribute.swift b/Sources/Plot/Internal/AnyAttribute.swift index 1624e57..8efdf3f 100644 --- a/Sources/Plot/Internal/AnyAttribute.swift +++ b/Sources/Plot/Internal/AnyAttribute.swift @@ -11,3 +11,9 @@ internal protocol AnyAttribute { func render() -> String } + +extension AnyAttribute { + var nonEmptyValue: String? { + value?.isEmpty == false ? value : nil + } +} diff --git a/Sources/Plot/Internal/ElementRenderingBuffer.swift b/Sources/Plot/Internal/ElementRenderingBuffer.swift index 1463df6..19bcd4f 100644 --- a/Sources/Plot/Internal/ElementRenderingBuffer.swift +++ b/Sources/Plot/Internal/ElementRenderingBuffer.swift @@ -20,12 +20,14 @@ internal final class ElementRenderingBuffer { func add(_ attribute: AnyAttribute) { if let existingIndex = attributeIndexes[attribute.name] { - if !attribute.replaceExisting, - let existingValue = attributes[existingIndex].value, - let newValue = attribute.value { - attributes[existingIndex].value = existingValue + " " + newValue - } else { + if attribute.replaceExisting { attributes[existingIndex].value = attribute.value + } else if let newValue = attribute.nonEmptyValue { + if let existingValue = attributes[existingIndex].nonEmptyValue { + attributes[existingIndex].value = existingValue + " " + newValue + } else { + attributes[existingIndex].value = newValue + } } } else { attributeIndexes[attribute.name] = attributes.count diff --git a/Tests/PlotTests/HTMLComponentTests.swift b/Tests/PlotTests/HTMLComponentTests.swift index fa872f2..2d435d6 100644 --- a/Tests/PlotTests/HTMLComponentTests.swift +++ b/Tests/PlotTests/HTMLComponentTests.swift @@ -88,6 +88,17 @@ final class HTMLComponentTests: XCTestCase { XCTAssertEqual(html, #"

Hello

"#) } + func testNotAppendingEmptyClassNames() { + let html = Paragraph("Hello") + .class("") + .class("one") + .class("") + .class("two") + .render() + + XCTAssertEqual(html, #"

Hello

"#) + } + func testReplacingClass() { let html = Paragraph("Hello") .class("one") From f4fd70667f35ce8dce302e240dacd9cf08a88cb5 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Wed, 12 May 2021 16:56:30 +0200 Subject: [PATCH 12/33] Make it possible to add attributes to wrapper components (#63) When an attribute is added to a component that acts as a wrapper for an element-based component, that attribute is now added directly to the wrapped, underlying element. That makes it possible to append things like class names to existing, custom components. --- Sources/Plot/API/Node.swift | 2 +- Tests/PlotTests/HTMLComponentTests.swift | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Sources/Plot/API/Node.swift b/Sources/Plot/API/Node.swift index 4c92ec4..acd0100 100644 --- a/Sources/Plot/API/Node.swift +++ b/Sources/Plot/API/Node.swift @@ -175,7 +175,7 @@ internal extension Node where Context == Any { static func modifiedComponent(_ component: ModifiedComponent) -> Node { Node { renderer in renderer.renderComponent(component.base, - deferredAttributes: component.deferredAttributes, + deferredAttributes: component.deferredAttributes + renderer.deferredAttributes, environmentOverrides: component.environmentOverrides ) } diff --git a/Tests/PlotTests/HTMLComponentTests.swift b/Tests/PlotTests/HTMLComponentTests.swift index 2d435d6..6491c62 100644 --- a/Tests/PlotTests/HTMLComponentTests.swift +++ b/Tests/PlotTests/HTMLComponentTests.swift @@ -88,7 +88,7 @@ final class HTMLComponentTests: XCTestCase { XCTAssertEqual(html, #"

Hello

"#) } - func testNotAppendingEmptyClassNames() { + func testNotAppendingEmptyClasses() { let html = Paragraph("Hello") .class("") .class("one") @@ -99,6 +99,23 @@ final class HTMLComponentTests: XCTestCase { XCTAssertEqual(html, #"

Hello

"#) } + func testAppendingClassesToWrappingComponents() { + struct InnerWrapper: Component { + var body: Component { + Paragraph("Hello").class("one") + } + } + + struct OuterWrapper: Component { + var body: Component { + InnerWrapper().class("two") + } + } + + let html = OuterWrapper().class("three").render() + XCTAssertEqual(html, #"

Hello

"#) + } + func testReplacingClass() { let html = Paragraph("Hello") .class("one") From cd6015944cdd37ca6a43e9ea86b3b34079d2b1cf Mon Sep 17 00:00:00 2001 From: John Sundell Date: Thu, 13 May 2021 12:04:01 +0200 Subject: [PATCH 13/33] Enable attributes to be appended to members of nested component groups (#64) Make it possible to append attributes to the member elements of a `ComponentGroup`, when such a group is nested within a custom wrapper component. --- Sources/Plot/API/Node.swift | 4 +++- Tests/PlotTests/HTMLComponentTests.swift | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Sources/Plot/API/Node.swift b/Sources/Plot/API/Node.swift index acd0100..245883e 100644 --- a/Sources/Plot/API/Node.swift +++ b/Sources/Plot/API/Node.swift @@ -184,7 +184,9 @@ internal extension Node where Context == Any { static func components(_ components: [Component]) -> Node { Node { renderer in components.forEach { - renderer.renderComponent($0) + renderer.renderComponent($0, + deferredAttributes: renderer.deferredAttributes + ) } } } diff --git a/Tests/PlotTests/HTMLComponentTests.swift b/Tests/PlotTests/HTMLComponentTests.swift index 6491c62..1c6d3d2 100644 --- a/Tests/PlotTests/HTMLComponentTests.swift +++ b/Tests/PlotTests/HTMLComponentTests.swift @@ -116,6 +116,21 @@ final class HTMLComponentTests: XCTestCase { XCTAssertEqual(html, #"

Hello

"#) } + func testAppendingClassToWrappingComponentContainingGroup() { + struct Wrapper: Component { + var body: Component { + ComponentGroup { + Paragraph("One") + Paragraph("Two") + } + .class("one") + } + } + + let html = Wrapper().class("two").render() + XCTAssertEqual(html, #"

One

Two

"#) + } + func testReplacingClass() { let html = Paragraph("Hello") .class("one") From c0bdc94c552bba1b0887103ebba6e20bfdf1f8d9 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Wed, 19 May 2021 10:37:06 +0200 Subject: [PATCH 14/33] Fix text rendering regression for indented documents (#65) Plot 0.9.0 introduced a minor regression for documents that are rendered with indentation, in that plain text nodes would always be rendered on a new line. This patch fixes that by differentiating between plain text output and element-based output, and only the latter causes a new line to be inserted, preserving the previous rendering behavior. Components that are made up of just plain text also get this behavior, and the unit test that verifies indented documents has been expanded to also cover these cases. --- .../Internal/ElementRenderingBuffer.swift | 4 +-- Sources/Plot/Internal/Renderer.swift | 25 +++++++++++++++---- Tests/PlotTests/DocumentTests.swift | 18 +++++++++---- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Sources/Plot/Internal/ElementRenderingBuffer.swift b/Sources/Plot/Internal/ElementRenderingBuffer.swift index 19bcd4f..6dc2ad1 100644 --- a/Sources/Plot/Internal/ElementRenderingBuffer.swift +++ b/Sources/Plot/Internal/ElementRenderingBuffer.swift @@ -35,8 +35,8 @@ internal final class ElementRenderingBuffer { } } - func add(_ text: String) { - if indentation != nil { + func add(_ text: String, isPlainText: Bool) { + if !isPlainText, indentation != nil { body.append("\n") } diff --git a/Sources/Plot/Internal/Renderer.swift b/Sources/Plot/Internal/Renderer.swift index abe193f..26fc906 100644 --- a/Sources/Plot/Internal/Renderer.swift +++ b/Sources/Plot/Internal/Renderer.swift @@ -12,6 +12,7 @@ internal struct Renderer { private var environment: Environment private var elementWrapper: ElementWrapper? private var elementBuffer: ElementRenderingBuffer? + private var containsElement = false } extension Renderer { @@ -30,7 +31,7 @@ extension Renderer { } mutating func renderRawText(_ text: String) { - renderRawText(text, wrapIfNeeded: true) + renderRawText(text, isPlainText: true, wrapIfNeeded: true) } mutating func renderText(_ text: String) { @@ -72,8 +73,13 @@ extension Renderer { } deferredAttributes.forEach(buffer.add) - renderRawText(buffer.flush(), wrapIfNeeded: false) elementBuffer?.containsChildElements = true + containsElement = true + + renderRawText(buffer.flush(), + isPlainText: false, + wrapIfNeeded: false + ) } mutating func renderAttribute(_ attribute: Attribute) { @@ -119,12 +125,21 @@ extension Renderer { ) } - renderRawText(renderer.result, wrapIfNeeded: false) + renderRawText(renderer.result, + isPlainText: !renderer.containsElement, + wrapIfNeeded: false + ) + + containsElement = renderer.containsElement } } private extension Renderer { - mutating func renderRawText(_ text: String, wrapIfNeeded: Bool) { + mutating func renderRawText( + _ text: String, + isPlainText: Bool, + wrapIfNeeded: Bool + ) { if wrapIfNeeded { if let wrapper = elementWrapper { return renderComponent(wrapper.body(Node.raw(text))) @@ -132,7 +147,7 @@ private extension Renderer { } if let elementBuffer = elementBuffer { - elementBuffer.add(text) + elementBuffer.add(text, isPlainText: isPlainText) } else { if indentation != nil && !result.isEmpty { result.append("\n") diff --git a/Tests/PlotTests/DocumentTests.swift b/Tests/PlotTests/DocumentTests.swift index 824ddcd..d1dd351 100644 --- a/Tests/PlotTests/DocumentTests.swift +++ b/Tests/PlotTests/DocumentTests.swift @@ -26,9 +26,16 @@ final class DocumentTests: XCTestCase { .element(named: "two", nodes: [ .selfClosedElement(named: "three") ]), - .element(named: "four") + .text("four "), + .component(Text("five")), + .component(Element.named("six", nodes: [ + .text("seven") + ])), + .element(named: "eight", nodes: [ + .text("nine") + ]) ]), - .selfClosed(named: "five", attributes: [ + .selfClosed(named: "ten", attributes: [ Attribute(name: "key", value: "value") ]) ] @@ -38,10 +45,11 @@ final class DocumentTests: XCTestCase { - - + four five + seven + nine - + """) } From 9cb3f163b0c423c6ed90e5d7e966ddcde7537f2b Mon Sep 17 00:00:00 2001 From: Hal Lee Date: Thu, 20 May 2021 06:31:40 -0400 Subject: [PATCH 15/33] Simpler docs + unit test --- Sources/Plot/API/HTMLViewportFitMode.swift | 13 +++++-------- Tests/PlotTests/HTMLTests.swift | 7 +++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Sources/Plot/API/HTMLViewportFitMode.swift b/Sources/Plot/API/HTMLViewportFitMode.swift index 8ea81f3..50af1e3 100644 --- a/Sources/Plot/API/HTMLViewportFitMode.swift +++ b/Sources/Plot/API/HTMLViewportFitMode.swift @@ -8,15 +8,12 @@ import Foundation /// Enum defining the fit parameters of the viewport meta tag. public enum HTMLViewportFitMode: String { - /// This value doesn’t affect the initial layout viewport, and the whole web page is viewable. - /// What the UA paints outside of the viewport is undefined. - /// It may be the background color of the canvas, or anything else that the UA deems appropriate. + /// The default viewport fit behavior. case auto - /// The initial layout viewport and the visual viewport are set to the largest rectangle which is - /// inscribed in the display of the device. What the UA paints outside of the viewport is undefined. - /// It may be the background color of the canvas, or anything else that the UA deems appropriate. + /// The initial layout viewport and the visual viewport are set + /// to fit within the safe area insets of the screen of the device. case contain - /// The initial layout viewport and the visual viewport - /// are set to the circumscribed rectangle of the physical screen of the device. + /// The initial layout viewport and the visual viewport are set + /// to the outer rectangle covering the screen, ignoring safe area insets. case cover } diff --git a/Tests/PlotTests/HTMLTests.swift b/Tests/PlotTests/HTMLTests.swift index 51f56d7..2997f16 100644 --- a/Tests/PlotTests/HTMLTests.swift +++ b/Tests/PlotTests/HTMLTests.swift @@ -113,6 +113,13 @@ final class HTMLTests: XCTestCase { """) } + + func testViewportFit() { + let html = HTML(.head(.viewport(.accordingToDevice, fit: .cover))) + assertEqualHTMLContent(html, """ + + """) + } func testFavicon() { let html = HTML(.head(.favicon("icon.png"))) From ae6a174ee6fe7968a4d648559bcb46e034b1ebe4 Mon Sep 17 00:00:00 2001 From: Hal Lee Date: Fri, 21 May 2021 12:58:30 -0400 Subject: [PATCH 16/33] Add doc comment --- Sources/Plot/API/HTMLComponents.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Plot/API/HTMLComponents.swift b/Sources/Plot/API/HTMLComponents.swift index d30bc5e..4bb35a8 100644 --- a/Sources/Plot/API/HTMLComponents.swift +++ b/Sources/Plot/API/HTMLComponents.swift @@ -86,6 +86,8 @@ public extension Node where Context == HTML.HeadContext { /// - parameter widthMode: How the viewport's width should scale according /// to the device the page is being rendered on. See `HTMLViewportWidthMode`. /// - parameter initialScale: The initial scale that the page should use. + /// - parameter fit: How the viewport should be laid out on screen in relation + /// to the screen’s safe area insets. See `HTMLViewportFitMode`. static func viewport(_ widthMode: HTMLViewportWidthMode, initialScale: Double = 1, fit: HTMLViewportFitMode? = nil) -> Node { From a31f8931c3b99e9f7451dced599c4b314f87d0ea Mon Sep 17 00:00:00 2001 From: Omar Albeik Date: Tue, 25 May 2021 11:12:23 +0200 Subject: [PATCH 17/33] Fix documentation typos (#67) --- Sources/Plot/API/Component.swift | 2 +- Sources/Plot/API/EnvironmentKey.swift | 2 +- Sources/Plot/API/PodcastElements.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Plot/API/Component.swift b/Sources/Plot/API/Component.swift index 29c6e29..b3c9292 100644 --- a/Sources/Plot/API/Component.swift +++ b/Sources/Plot/API/Component.swift @@ -80,7 +80,7 @@ public extension Component { /// Place a value into the environment used to render this component and any /// of its child components. An environment value will be passed downwards - /// through a component/node hierarchy until its overriden by another value + /// through a component/node hierarchy until its overridden by another value /// for the same key. /// - parameter value: The value to add. Must match the type of the key that /// it's being added for. This value will override any value that was assigned diff --git a/Sources/Plot/API/EnvironmentKey.swift b/Sources/Plot/API/EnvironmentKey.swift index 06a1bc8..10fda7b 100644 --- a/Sources/Plot/API/EnvironmentKey.swift +++ b/Sources/Plot/API/EnvironmentKey.swift @@ -7,7 +7,7 @@ import Foundation /// Type used to define an environment key, which can be used to pass a given -/// value downward through a component/node hierarchy until its overriden by +/// value downward through a component/node hierarchy until its overridden by /// another value for the same key. You can place values into the environment /// using the `environmentValue` modifier, and you can then retrieve those /// values within any component using the `EnvironmentValue` property wrapper. diff --git a/Sources/Plot/API/PodcastElements.swift b/Sources/Plot/API/PodcastElements.swift index ba85b83..a503632 100644 --- a/Sources/Plot/API/PodcastElements.swift +++ b/Sources/Plot/API/PodcastElements.swift @@ -118,7 +118,7 @@ public extension Node where Context == PodcastFeed.ItemContext { /// Define the duration of the episode as a string. /// - /// Consider using the more type-safe `hours:minues:seconds:` variant + /// Consider using the more type-safe `hours:minutes:seconds:` variant /// if you're defining a duration in code. /// /// - parameter string: A string that describes the episode's duration. @@ -204,7 +204,7 @@ public extension Node where Context == PodcastFeed.MediaContext { } /// Define the media item's title (usually the episode's title). - /// - paramter title: The title to define. + /// - Parameter title: The title to define. static func title(_ title: String) -> Node { .element(named: "media:title", nodes: [ .attribute(named: "type", value: "plain"), From 574b1ae2ffc45beeb95d56fe5df91e493d42c1b9 Mon Sep 17 00:00:00 2001 From: Dave Verwer Date: Tue, 25 May 2021 10:13:29 +0100 Subject: [PATCH 18/33] Added a `description` method to `RSSContentContext` which supports HTML content (#66) * Added a `description` method to `RSSContentContext` which supports HTML content. * Added a test for the `description` method that supports HTML content. --- Sources/Plot/API/RSSElements.swift | 6 ++++++ Tests/PlotTests/RSSTests.swift | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Sources/Plot/API/RSSElements.swift b/Sources/Plot/API/RSSElements.swift index 2f28387..4827426 100644 --- a/Sources/Plot/API/RSSElements.swift +++ b/Sources/Plot/API/RSSElements.swift @@ -115,6 +115,12 @@ public extension Node where Context: RSSContentContext { .element(named: "description", text: text) } + /// Define a description for the content as CDATA encoded HTML. + /// - parameter nodes: The HTML nodes to render as a description. + static func description(_ nodes: Node...) -> Node { + .element(named: "description", nodes: [Node.raw("")]) + } + /// Define the content's canonical URL. /// - parameter url: The content's URL. static func link(_ url: URLRepresentable) -> Node { diff --git a/Tests/PlotTests/RSSTests.swift b/Tests/PlotTests/RSSTests.swift index 1cf2f72..8d45f4e 100644 --- a/Tests/PlotTests/RSSTests.swift +++ b/Tests/PlotTests/RSSTests.swift @@ -23,6 +23,19 @@ final class RSSTests: XCTestCase { assertEqualRSSFeedContent(feed, "Description") } + func testFeedDescriptionWithHTMLContent() { + let feed = RSS( + .description( + .p( + .text("Description with "), + .em("emphasis"), + .text(".") + ) + ) + ) + assertEqualRSSFeedContent(feed, "Description with emphasis.

]]>
") + } + func testFeedURL() { let feed = RSS(.link("url.com")) assertEqualRSSFeedContent(feed, "url.com") From d25a6964636d9135aba5e636949c8942635f8a1f Mon Sep 17 00:00:00 2001 From: Dave Verwer Date: Tue, 1 Jun 2021 13:54:13 +0100 Subject: [PATCH 19/33] Moved the `title` attribute to be a global attribute and added an `Attribute` method. (#71) --- Sources/Plot/API/HTMLAttributes.swift | 22 ++++++++++++---------- Tests/PlotTests/HTMLTests.swift | 22 +++++++++++++++++----- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Sources/Plot/API/HTMLAttributes.swift b/Sources/Plot/API/HTMLAttributes.swift index 22898b5..a9b45a7 100644 --- a/Sources/Plot/API/HTMLAttributes.swift +++ b/Sources/Plot/API/HTMLAttributes.swift @@ -27,6 +27,12 @@ public extension Attribute where Context: HTMLContext { static func data(named name: String, value: String) -> Attribute { Attribute(name: "data-\(name)", value: value) } + + /// Specify a title for the element. + /// - parameter title: The title to assign to the element. + static func title(_ title: String) -> Attribute { + Attribute(name: "title", value: title) + } } public extension Node where Context: HTMLContext { @@ -50,6 +56,12 @@ public extension Node where Context: HTMLContext { static func data(named name: String, value: String) -> Node { .attribute(named: "data-\(name)", value: value) } + + /// Specify a title for the element. + /// - parameter title: The title to assign to the element. + static func title(_ title: String) -> Node { + .attribute(named: "title", value: title) + } } public extension Attribute where Context: HTMLNamableContext { @@ -102,16 +114,6 @@ public extension Node where Context == HTML.DocumentContext { } } -// MARK: - Body - -public extension Node where Context: HTML.BodyContext { - /// Specify a title for the element. - /// - parameter title: The title to assign to the element. - static func title(_ title: String) -> Node { - .attribute(named: "title", value: title) - } -} - // MARK: - Links public extension Attribute where Context == HTML.LinkContext { diff --git a/Tests/PlotTests/HTMLTests.swift b/Tests/PlotTests/HTMLTests.swift index 2a94c8b..ca91c7c 100644 --- a/Tests/PlotTests/HTMLTests.swift +++ b/Tests/PlotTests/HTMLTests.swift @@ -191,14 +191,26 @@ final class HTMLTests: XCTestCase { } func testTitleAttribute() { - let html = HTML(.body( - .div(.title("Division title"), - .p(.title("Paragraph title"), "Paragraph"), - .a(.href("#"), .title("Link title"), "Link") + let html = HTML( + .head( + .link( + .rel(.alternate), + .title("Alternative representation") + ) + ), + .body( + .div( + .title("Division title"), + .p(.title("Paragraph title"), "Paragraph"), + .a(.href("#"), .title("Link title"), "Link") + ) ) - )) + ) assertEqualHTMLContent(html, """ + \ + \ + \ \
\

Paragraph

\ From 1b10c15e006bd89deb13c1a9e1ad2150fdbbf7ed Mon Sep 17 00:00:00 2001 From: Dave Verwer Date: Tue, 1 Jun 2021 14:10:24 +0100 Subject: [PATCH 20/33] Added a global `spellcheck` attribute and tests with common use cases (#70) --- Sources/Plot/API/HTMLAttributes.swift | 12 ++++++++++++ Tests/PlotTests/HTMLTests.swift | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Sources/Plot/API/HTMLAttributes.swift b/Sources/Plot/API/HTMLAttributes.swift index a9b45a7..82e8faa 100644 --- a/Sources/Plot/API/HTMLAttributes.swift +++ b/Sources/Plot/API/HTMLAttributes.swift @@ -28,6 +28,12 @@ public extension Attribute where Context: HTMLContext { Attribute(name: "data-\(name)", value: value) } + /// Assign whether operating system level spell checking should be enabled. + /// - parameter isEnabled: Whether spell checking should be enabled. + static func spellcheck(_ isEnabled: Bool) -> Attribute { + Attribute(name: "spellcheck", value: String(isEnabled)) + } + /// Specify a title for the element. /// - parameter title: The title to assign to the element. static func title(_ title: String) -> Attribute { @@ -57,6 +63,12 @@ public extension Node where Context: HTMLContext { .attribute(named: "data-\(name)", value: value) } + /// Assign whether operating system level spell checking should be enabled. + /// - parameter isEnabled: Whether spell checking should be enabled. + static func spellcheck(_ isEnabled: Bool) -> Node { + .attribute(named: "spellcheck", value: String(isEnabled)) + } + /// Specify a title for the element. /// - parameter title: The title to assign to the element. static func title(_ title: String) -> Node { diff --git a/Tests/PlotTests/HTMLTests.swift b/Tests/PlotTests/HTMLTests.swift index ca91c7c..fa17f45 100644 --- a/Tests/PlotTests/HTMLTests.swift +++ b/Tests/PlotTests/HTMLTests.swift @@ -735,6 +735,26 @@ final class HTMLTests: XCTestCase { """) } + + func testSpellcheckAttribute() { + let html = HTML( + .body( + .spellcheck(true), + .form( + .input(.type(.text), .spellcheck(false)), + .textarea(.spellcheck(false)) + ) + ) + ) + assertEqualHTMLContent(html, """ + \ +
\ + \ + \ +
\ + + """) + } func testSubresourceIntegrity() { let html = HTML(.head( From 0320a8da4845c5f2549a211b39b230637f7952aa Mon Sep 17 00:00:00 2001 From: Dave Verwer Date: Tue, 1 Jun 2021 16:53:46 +0100 Subject: [PATCH 21/33] Added an attribute to represent the button types. (#69) * Added `HTMLButtonType`. * Added a test for button type. --- Sources/Plot/API/HTMLAttributes.swift | 8 ++++++++ Sources/Plot/API/HTMLButtonType.swift | 15 +++++++++++++++ Tests/PlotTests/HTMLTests.swift | 8 ++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 Sources/Plot/API/HTMLButtonType.swift diff --git a/Sources/Plot/API/HTMLAttributes.swift b/Sources/Plot/API/HTMLAttributes.swift index 82e8faa..db87b87 100644 --- a/Sources/Plot/API/HTMLAttributes.swift +++ b/Sources/Plot/API/HTMLAttributes.swift @@ -311,6 +311,14 @@ public extension Attribute where Context == HTML.InputContext { } } +public extension Node where Context == HTML.ButtonContext { + /// Assign a button type to the element. + /// - parameter type: The button type to assign. + static func type(_ type: HTMLButtonType) -> Node { + .attribute(named: "type", value: type.rawValue) + } +} + public extension Node where Context == HTML.TextAreaContext { /// Specify the number of columns that the text area should contain. /// - parameter columns: The number of columns to specify. diff --git a/Sources/Plot/API/HTMLButtonType.swift b/Sources/Plot/API/HTMLButtonType.swift new file mode 100644 index 0000000..f1634c0 --- /dev/null +++ b/Sources/Plot/API/HTMLButtonType.swift @@ -0,0 +1,15 @@ +/** +* Plot +* Copyright (c) John Sundell 2019 +* MIT license, see LICENSE file for details +*/ + +import Foundation + +/// Enum that defines various button types that can be used with the +/// ` + \ + \ + \ + """) } From c27f6857deeb4c4623db294172a8ec572790a9be Mon Sep 17 00:00:00 2001 From: Dave Verwer Date: Tue, 1 Jun 2021 16:56:34 +0100 Subject: [PATCH 22/33] Boolean attributes (#68) * Change `required` and `autofocus` to be boolean attributes. * Added `readonly`, `disabled`, and `multiple` boolean attributes to `input` elements. * Added `readonly ` and `disabled ` attributes to the `textarea` element. * Added a `placeholder` attribute to the `textarea` element. * Changed the `allowfullscreen` attribute on the `iframe` element to be a boolean attribute. * Added an `open` boolean attribute to the `details` element. * Added the `hidden` boolean attribute as a global attribute. * Added `checked` boolean attribute to the `input` element. * Added a `name` to the `file` input element in the form test. --- Sources/Plot/API/HTMLAttributes.swift | 70 ++++++++++++++++++++++-- Tests/PlotTests/HTMLComponentTests.swift | 4 +- Tests/PlotTests/HTMLTests.swift | 44 +++++++++++---- 3 files changed, 100 insertions(+), 18 deletions(-) diff --git a/Sources/Plot/API/HTMLAttributes.swift b/Sources/Plot/API/HTMLAttributes.swift index db87b87..9fa1c3d 100644 --- a/Sources/Plot/API/HTMLAttributes.swift +++ b/Sources/Plot/API/HTMLAttributes.swift @@ -74,6 +74,12 @@ public extension Node where Context: HTMLContext { static func title(_ title: String) -> Node { .attribute(named: "title", value: title) } + + /// Assign whether the element should be hidden. + /// - parameter isHidden: Whether the element should be hidden or not. + static func hidden(_ isHidden: Bool) -> Node { + isHidden ? .attribute(named: "hidden") : .empty + } } public extension Attribute where Context: HTMLNamableContext { @@ -185,6 +191,16 @@ public extension Node where Context == HTML.AnchorContext { } } +// MARK: - Interactive elements + +public extension Node where Context == HTML.DetailsContext { + /// Assign whether the details element is opened/expanded. + /// - parameter isOpen: Whether the element should be displayed as open. + static func open(_ isOpen: Bool) -> Node { + isOpen ? .attribute(named: "open") : .empty + } +} + // MARK: - Sources and media public extension Attribute where Context: HTMLSourceContext { @@ -286,7 +302,7 @@ public extension Attribute where Context == HTML.InputContext { Attribute(name: "type", value: type.rawValue) } - /// Assigns a placeholder to the input field. + /// Assign a placeholder to the input field. /// - parameter placeholder: The placeholder to assign. static func placeholder(_ placeholder: String) -> Attribute { Attribute(name: "placeholder", value: placeholder) @@ -301,13 +317,37 @@ public extension Attribute where Context == HTML.InputContext { /// Assign whether the element is required before submitting the form. /// - parameter isRequired: Whether the element is required. static func required(_ isRequired: Bool) -> Attribute { - isRequired ? Attribute(name: "required", value: "true") : .empty + isRequired ? Attribute(name: "required", value: nil, ignoreIfValueIsEmpty: false) : .empty } /// Assign whether the element should be autofocused when the page loads. /// - parameter isOn: Whether autofocus should be turned on. static func autofocus(_ isOn: Bool) -> Attribute { - isOn ? Attribute(name: "autofocus", value: "true") : .empty + isOn ? Attribute(name: "autofocus", value: nil, ignoreIfValueIsEmpty: false) : .empty + } + + /// Assign whether the element should be read-only. + /// - parameter isReadonly: Whether the input is read-only. + static func readonly(_ isReadonly: Bool) -> Attribute { + isReadonly ? Attribute(name: "readonly", value: nil, ignoreIfValueIsEmpty: false) : .empty + } + + /// Assign whether the element should be disabled. + /// - parameter isDisabled: Whether the input is disabled. + static func disabled(_ isDisabled: Bool) -> Attribute { + isDisabled ? Attribute(name: "disabled", value: nil, ignoreIfValueIsEmpty: false) : .empty + } + + /// Assign whether the element should allow the selection of multiple values. + /// - parameter isMultiple: Whether multiple values are allowed. + static func multiple(_ isEnabled: Bool) -> Attribute { + isEnabled ? Attribute(name: "multiple", value: nil, ignoreIfValueIsEmpty: false) : .empty + } + + /// Assign whether a checkbox or radio input element has an active state. + /// - parameter isChecked: Whether the element has an active state. + static func checked(_ isChecked: Bool) -> Attribute { + isChecked ? Attribute(name: "checked", value: nil, ignoreIfValueIsEmpty: false) : .empty } } @@ -332,16 +372,34 @@ public extension Node where Context == HTML.TextAreaContext { .attribute(named: "rows", value: String(rows)) } + /// Assign a placeholder to the text area. + /// - parameter placeholder: The placeholder to assign. + static func placeholder(_ placeholder: String) -> Node { + .attribute(named: "placeholder", value: placeholder) + } + /// Assign whether the element is required before submitting the form. /// - parameter isRequired: Whether the element is required. static func required(_ isRequired: Bool) -> Node { - isRequired ? .attribute(named: "required", value: "true") : .empty + isRequired ? .attribute(named: "required") : .empty } /// Assign whether the element should be autofocused when the page loads. /// - parameter isOn: Whether autofocus should be turned on. static func autofocus(_ isOn: Bool) -> Node { - isOn ? .attribute(named: "autofocus", value: "true") : .empty + isOn ? .attribute(named: "autofocus") : .empty + } + + /// Assign whether the element should be read-only. + /// - parameter isReadonly: Whether the input is read-only. + static func readonly(_ isReadonly: Bool) -> Node { + isReadonly ? .attribute(named: "readonly") : .empty + } + + /// Assign whether the element should be disabled. + /// - parameter isDisabled: Whether the input is disabled. + static func disabled(_ isDisabled: Bool) -> Node { + isDisabled ? .attribute(named: "disabled") : .empty } } @@ -423,7 +481,7 @@ public extension Attribute where Context == HTML.IFrameContext { /// Assign whether to grant the iframe full screen capabilities. /// - parameter allow: Whether the iframe should be allowed to go full screen. static func allowfullscreen(_ allow: Bool) -> Attribute { - Attribute(name: "allowfullscreen", value: String(allow)) + allow ? Attribute(name: "allowfullscreen", value: nil, ignoreIfValueIsEmpty: false) : .empty } } diff --git a/Tests/PlotTests/HTMLComponentTests.swift b/Tests/PlotTests/HTMLComponentTests.swift index 1c6d3d2..42dd660 100644 --- a/Tests/PlotTests/HTMLComponentTests.swift +++ b/Tests/PlotTests/HTMLComponentTests.swift @@ -364,7 +364,7 @@ final class HTMLComponentTests: XCTestCase {
\
\ \
\ - \ + \ \ - \ - \ - \ + \ + \ + \ + \ + \ + \ + \ \
""") @@ -577,13 +593,17 @@ final class HTMLTests: XCTestCase { .src("url.com"), .frameborder(false), .allow("gyroscope"), + .allowfullscreen(false) + ), + .iframe( .allowfullscreen(true) ) )) assertEqualHTMLContent(html, """ \ - \ + \ + \ """) } @@ -657,11 +677,15 @@ final class HTMLTests: XCTestCase { func testDetails() { let html = HTML(.body( - .details(.summary("Summary"), .p("Text")) + .details(.open(true), .summary("Open Summary"), .p("Text")), + .details(.open(false), .summary("Closed Summary"), .p("Text")) )) assertEqualHTMLContent(html, """ -
Summary

Text

+ \ +
Open Summary

Text

\ +
Closed Summary

Text

\ + """) } From a2f7767967356a9d7d1de40e2701a915dc7cc79e Mon Sep 17 00:00:00 2001 From: Abdulaziz Alobaili Date: Sun, 20 Jun 2021 18:34:02 +0300 Subject: [PATCH 23/33] Add the dir attribute --- Sources/Plot/API/Directionality.swift | 12 ++++++ Sources/Plot/API/HTMLAttributes.swift | 12 ++++++ Tests/PlotTests/HTMLTests.swift | 55 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 Sources/Plot/API/Directionality.swift diff --git a/Sources/Plot/API/Directionality.swift b/Sources/Plot/API/Directionality.swift new file mode 100644 index 0000000..7547e70 --- /dev/null +++ b/Sources/Plot/API/Directionality.swift @@ -0,0 +1,12 @@ +/** + * Plot + * Copyright (c) John Sundell 2021 + * MIT license, see LICENSE file for details + */ + +/// Enum defining an element's text directionality. +public enum Directionality: String { + case leftToRight = "ltr" + case rightToLeft = "rtl" + case auto = "auto" +} diff --git a/Sources/Plot/API/HTMLAttributes.swift b/Sources/Plot/API/HTMLAttributes.swift index 9fa1c3d..40cdc4a 100644 --- a/Sources/Plot/API/HTMLAttributes.swift +++ b/Sources/Plot/API/HTMLAttributes.swift @@ -39,6 +39,12 @@ public extension Attribute where Context: HTMLContext { static func title(_ title: String) -> Attribute { Attribute(name: "title", value: title) } + + /// Specify a directionality for the element. + /// - parameter directionality: The directionality to assign to the element. + static func dir(_ directionality: Directionality) -> Attribute { + Attribute(name: "dir", value: directionality.rawValue) + } } public extension Node where Context: HTMLContext { @@ -80,6 +86,12 @@ public extension Node where Context: HTMLContext { static func hidden(_ isHidden: Bool) -> Node { isHidden ? .attribute(named: "hidden") : .empty } + + /// Specify a directionality for the element. + /// - parameter directionality: The directionality to assign to the element. + static func dir(_ directionality: Directionality) -> Node { + .attribute(named: "dir", value: directionality.rawValue) + } } public extension Attribute where Context: HTMLNamableContext { diff --git a/Tests/PlotTests/HTMLTests.swift b/Tests/PlotTests/HTMLTests.swift index 2dd54bb..9da9b6e 100644 --- a/Tests/PlotTests/HTMLTests.swift +++ b/Tests/PlotTests/HTMLTests.swift @@ -17,6 +17,21 @@ final class HTMLTests: XCTestCase { XCTAssertEqual(html.render(), #""#) } + func testPageDirectionalityLeftToRight() { + let html = HTML(.dir(.leftToRight)) + XCTAssertEqual(html.render(), #""#) + } + + func testPageDirectionalityRightToLeft() { + let html = HTML(.dir(.rightToLeft)) + XCTAssertEqual(html.render(), #""#) + } + + func testPageDirectionalityAuto() { + let html = HTML(.dir(.auto)) + XCTAssertEqual(html.render(), #""#) + } + func testHeadAndBody() { let html = HTML(.head(), .body()) assertEqualHTMLContent(html, "") @@ -249,6 +264,46 @@ final class HTMLTests: XCTestCase { """) } + func testTextDirectionalityLeftToRight() { + let html = HTML(.body( + .h1(.dir(.leftToRight), "Text") + )) + + assertEqualHTMLContent(html, #"

Text

"#) + } + + func testTextDirectionalityRightToLeft() { + let html = HTML(.body( + .h1(.dir(.rightToLeft), "Text") + )) + + assertEqualHTMLContent(html, #"

Text

"#) + } + + func testTextDirectionalityAuto() { + let html = HTML(.body( + .h1(.dir(.auto), "Text") + )) + + assertEqualHTMLContent(html, #"

Text

"#) + } + + func testInputDirectionalityAuto() { + let html = HTML(.body( + .input(.dir(.auto)) + )) + + assertEqualHTMLContent(html, #""#) + } + + func testTextAreaDirectionalityLeftToRight() { + let html = HTML(.body( + .textarea(.dir(.auto)) + )) + + assertEqualHTMLContent(html, #""#) + } + func testAnchors() throws { let html = try HTML(.body( .a(.href("a.html"), .target(.blank), .text("A")), From cc7e2b65b132fa217047799a93788c08a44eaa09 Mon Sep 17 00:00:00 2001 From: Abdulaziz Alobaili Date: Sun, 20 Jun 2021 19:03:33 +0300 Subject: [PATCH 24/33] Add the dir attribute as a component modifier --- Sources/Plot/API/ComponentAttributes.swift | 6 ++++++ Tests/PlotTests/HTMLComponentTests.swift | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/Sources/Plot/API/ComponentAttributes.swift b/Sources/Plot/API/ComponentAttributes.swift index 2444aea..494c00c 100644 --- a/Sources/Plot/API/ComponentAttributes.swift +++ b/Sources/Plot/API/ComponentAttributes.swift @@ -40,6 +40,12 @@ public extension Component { attribute(named: "id", value: id) } + /// Assign a directionality to this component's element. + /// - parameter directionality: The directionality to assign. + func dir(_ directionality: Directionality) -> Component { + attribute(named: "dir", value: directionality.rawValue) + } + /// Assign whether this component hierarchy's `Input` components should have /// autocomplete turned on or off. This value is placed in the environment, and /// is thus inherited by all child components. Note that this modifier only diff --git a/Tests/PlotTests/HTMLComponentTests.swift b/Tests/PlotTests/HTMLComponentTests.swift index 42dd660..4498bc3 100644 --- a/Tests/PlotTests/HTMLComponentTests.swift +++ b/Tests/PlotTests/HTMLComponentTests.swift @@ -78,6 +78,14 @@ final class HTMLComponentTests: XCTestCase { """) } + func testAssigningDirectionalityToElement() { + let html = Paragraph("Hello") + .dir(.leftToRight) + .render() + + XCTAssertEqual(html, #"

Hello

"#) + } + func testAppendingClasses() { let html = Paragraph("Hello") .class("one") From 162f6eb390ac61edfd40e142c6904ccc89318101 Mon Sep 17 00:00:00 2001 From: Patrick Date: Thu, 26 Aug 2021 09:14:53 +0400 Subject: [PATCH 25/33] Update README.md to mark a codeblock as being `swift` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9238c3..7286785 100644 --- a/README.md +++ b/README.md @@ -454,7 +454,7 @@ let string = header.render() Just like nodes, components can also be rendered on their own: -``` +```swift let header = Header { H1("Title") Span("Description") From d85439eaf4b91cf8a567828dba644d09b02d6248 Mon Sep 17 00:00:00 2001 From: Emory Dunn Date: Tue, 28 Sep 2021 14:46:48 -0700 Subject: [PATCH 26/33] Replace enum with RawRepresentable struct --- Sources/Plot/API/HTMLAnchorRelationship.swift | 68 ++++++++++++++++--- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/Sources/Plot/API/HTMLAnchorRelationship.swift b/Sources/Plot/API/HTMLAnchorRelationship.swift index d5bace3..5bb3e04 100644 --- a/Sources/Plot/API/HTMLAnchorRelationship.swift +++ b/Sources/Plot/API/HTMLAnchorRelationship.swift @@ -9,17 +9,67 @@ import Foundation /// An enum that defines various values for an HTML anchor's `rel` /// attribute, which specifies the relationship that the anchor has /// to the URL that it's linking to. -public enum HTMLAnchorRelationship: String { - /// Instructs bots, indexers and parsers that the link should - /// not be followed when parsing the current page. - case nofollow - case noopener - case noreferrer - case opener - case external +public struct HTMLAnchorRelationship: RawRepresentable, Identifiable, ExpressibleByStringLiteral { + public var rawValue: String + + public var id: String { rawValue } + + public init(rawValue: String) { + self.rawValue = rawValue + } + + public init(stringLiteral value: StringLiteralType) { + self.rawValue = value + } + + // MARK: Default Values + + /// Provides a link to an alternate representation of the document (i.e. print page, translated or mirror) + public static let alternate: HTMLAnchorRelationship = "alternate" + + /// Provides a link to the author of the document + public static let author: HTMLAnchorRelationship = "author" + + /// Permanent URL used for bookmarking + public static let bookmark: HTMLAnchorRelationship = "bookmark" + + /// Indicates that the referenced document is not part of the same site as the current document + public static let external: HTMLAnchorRelationship = "external" + + /// Provides a link to a help document + public static let help: HTMLAnchorRelationship = "help" + + /// Provides a link to licensing information for the document + public static let license: HTMLAnchorRelationship = "license" + + /// Provides a link to the next document in the series + public static let next: HTMLAnchorRelationship = "next" + + /// Links to an unendorsed document, like a paid link. + /// - Note: "nofollow" is used by Google, to specify that the Google search spider should not follow that link + public static let nofollow: HTMLAnchorRelationship = "nofollow" + + /// Requires that any browsing context created by following the hyperlink must not have an opener browsing context + public static let noopener: HTMLAnchorRelationship = "noopener" + + /// Makes the referrer unknown. No referer header will be included when the user clicks the hyperlink + public static let noreferrer: HTMLAnchorRelationship = "noreferrer" + + /// The previous document in a selection + public static let prev: HTMLAnchorRelationship = "prev" + + /// Links to a search tool for the document + public static let search: HTMLAnchorRelationship = "search" + + /// A tag (keyword) for the current document + public static let tag: HTMLAnchorRelationship = "tag" + /// For displaying augmented reality content in iOS Safari. /// Adding this tag will instruct Safari to directly open the content /// rather than navigating to a new page. /// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/ - case ar + public static let ar: HTMLAnchorRelationship = "ar" + + public static let opener: HTMLAnchorRelationship = "opener" + } From b358860fe565eb53e98b1f5807eb5939c8124547 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Tue, 15 Mar 2022 17:58:17 +0100 Subject: [PATCH 27/33] Fix compiler error and warning introduced in Swift 5.6 (#80) - Replace `HTMLAnchorTarget.self` with `.current` (while still maintaining backward compatibility with `.self` using a deprecated computed property). - Use per-`init` generic type constraints for optional `EnvironmentKey.Value` types rather than applying an extension-wide constraint using `ExpressibleByNilLiteral`. --- Sources/Plot/API/EnvironmentKey.swift | 6 +++--- Sources/Plot/API/HTMLAnchorTarget.swift | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/Plot/API/EnvironmentKey.swift b/Sources/Plot/API/EnvironmentKey.swift index 10fda7b..e5eb081 100644 --- a/Sources/Plot/API/EnvironmentKey.swift +++ b/Sources/Plot/API/EnvironmentKey.swift @@ -39,11 +39,11 @@ public extension EnvironmentKey { } } -public extension EnvironmentKey where Value: ExpressibleByNilLiteral { +public extension EnvironmentKey { /// Initialize a key with an explicit identifier. /// - parameter identifier: The identifier that the key should have. Must /// be a static string that's defined using a compile time literal. - init(identifier: StaticString) { + init(identifier: StaticString) where Value == T? { self.init(identifier: identifier, defaultValue: nil) } @@ -51,7 +51,7 @@ public extension EnvironmentKey where Value: ExpressibleByNilLiteral { /// be computed based on the name of the property or function that created it. /// - parameter autoIdentifier: This parameter will be filled in by the /// compiler based on the name of the call site's enclosing function/property. - init(autoIdentifier: StaticString = #function) { + init(autoIdentifier: StaticString = #function) where Value == T? { self.init(identifier: autoIdentifier, defaultValue: nil) } } diff --git a/Sources/Plot/API/HTMLAnchorTarget.swift b/Sources/Plot/API/HTMLAnchorTarget.swift index 0eff6bd..ba7f890 100644 --- a/Sources/Plot/API/HTMLAnchorTarget.swift +++ b/Sources/Plot/API/HTMLAnchorTarget.swift @@ -10,7 +10,7 @@ import Foundation /// attribute, which specifies how its URL should be opened. public enum HTMLAnchorTarget: String { /// The URL should be opened in the current browser context (default). - case `self` = "self" + case current = "self" /// The URL should be opened in a new, blank tab or window. case blank = "_blank" /// The URL should be opened in any parent frame. @@ -18,3 +18,8 @@ public enum HTMLAnchorTarget: String { /// The URL should be opened in the topmost frame. case top = "_top" } + +extension HTMLAnchorTarget { + @available(*, deprecated, message: "Use .current instead") + static var `self`: Self { current } +} From 877f6451612b969e87df83fc9cd933e9668dd4ff Mon Sep 17 00:00:00 2001 From: Jordan Kay Date: Wed, 20 Apr 2022 07:14:01 -0400 Subject: [PATCH 28/33] Fix typo in the documentation for EnvironmentValue (#83) --- Sources/Plot/API/EnvironmentValue.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Plot/API/EnvironmentValue.swift b/Sources/Plot/API/EnvironmentValue.swift index 3a3ab15..2d5b661 100644 --- a/Sources/Plot/API/EnvironmentValue.swift +++ b/Sources/Plot/API/EnvironmentValue.swift @@ -11,7 +11,7 @@ import Foundation /// You can annotate any `Component` property with the `@EnvironmentValue` attribute /// to have its value be determined by the environment. Environment values are always /// associated with an `EnvironmentKey`, and are passed downwards through a component/node -/// hierarchy until overriden by another value. +/// hierarchy until overridden by another value. @propertyWrapper public struct EnvironmentValue: AnyEnvironmentValue { /// The underlying value of the wrapped property. public var wrappedValue: Value { environment.value?[key] ?? key.defaultValue } From 2b6fcacff90343cfbaa8b7b21622aa14984b6898 Mon Sep 17 00:00:00 2001 From: Charlie Fish Date: Wed, 20 Apr 2022 05:19:15 -0600 Subject: [PATCH 29/33] Adding type attribute to PictureSourceContext (#76) --- Sources/Plot/API/HTMLAttributes.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Plot/API/HTMLAttributes.swift b/Sources/Plot/API/HTMLAttributes.swift index 9fa1c3d..9c9b8f6 100644 --- a/Sources/Plot/API/HTMLAttributes.swift +++ b/Sources/Plot/API/HTMLAttributes.swift @@ -255,6 +255,12 @@ public extension Attribute where Context == HTML.PictureSourceContext { static func media(_ query: String) -> Attribute { Attribute(name: "media", value: query) } + + /// Assign a string describing the MIME type, using the `type` attribute. + /// - parameter type: The type (MIME type) for this element. + static func type(_ type: String) -> Attribute { + Attribute(name: "type", value: type) + } } // MARK: - Forms, input and options From 606063faa3145423f4515fe57b4aed8887efde79 Mon Sep 17 00:00:00 2001 From: Zeng Guojie Date: Sat, 6 Aug 2022 18:40:36 +0800 Subject: [PATCH 30/33] Add languages. --- Sources/Plot/API/Language.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Plot/API/Language.swift b/Sources/Plot/API/Language.swift index ad6e864..ac5a16f 100644 --- a/Sources/Plot/API/Language.swift +++ b/Sources/Plot/API/Language.swift @@ -41,6 +41,8 @@ public enum Language: String { case chechen = "ce" case chichewa, chewa, nyanja = "ny" case chinese = "zh" + case traditionalChinese = "zh-Hant" + case simplifiedChinese = "zh-Hans" case chuvash = "cv" case cornish = "kw" case corsican = "co" From 066cce06e1a4a819b290207e1657e650f00fad49 Mon Sep 17 00:00:00 2001 From: John Sundell Date: Fri, 9 Dec 2022 12:39:54 +0100 Subject: [PATCH 31/33] HTMLAnchorRelationship: Minor fixes Minor code style and documentation fixes --- Sources/Plot/API/HTMLAnchorRelationship.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Plot/API/HTMLAnchorRelationship.swift b/Sources/Plot/API/HTMLAnchorRelationship.swift index 5bb3e04..dd29479 100644 --- a/Sources/Plot/API/HTMLAnchorRelationship.swift +++ b/Sources/Plot/API/HTMLAnchorRelationship.swift @@ -10,9 +10,8 @@ import Foundation /// attribute, which specifies the relationship that the anchor has /// to the URL that it's linking to. public struct HTMLAnchorRelationship: RawRepresentable, Identifiable, ExpressibleByStringLiteral { - public var rawValue: String - public var id: String { rawValue } + public var rawValue: String public init(rawValue: String) { self.rawValue = rawValue @@ -70,6 +69,6 @@ public struct HTMLAnchorRelationship: RawRepresentable, Identifiable, Expressibl /// https://webkit.org/blog/8421/viewing-augmented-reality-assets-in-safari-for-ios/ public static let ar: HTMLAnchorRelationship = "ar" + /// The opposite of `noopener`. public static let opener: HTMLAnchorRelationship = "opener" - } From 19c619a03ef3898a5ceed250f9e681d721885faf Mon Sep 17 00:00:00 2001 From: John Sundell Date: Fri, 31 Mar 2023 15:23:06 +0200 Subject: [PATCH 32/33] Use full name for Component directionality API Component modifiers should use Swift's default naming conventions, and not include abbreviations that are commonly used within HTML's naming conventions. Co-authored-by: Dorian <6209874+MrSkwiggs@users.noreply.github.com> --- Sources/Plot/API/ComponentAttributes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Plot/API/ComponentAttributes.swift b/Sources/Plot/API/ComponentAttributes.swift index 494c00c..ca3b37c 100644 --- a/Sources/Plot/API/ComponentAttributes.swift +++ b/Sources/Plot/API/ComponentAttributes.swift @@ -42,7 +42,7 @@ public extension Component { /// Assign a directionality to this component's element. /// - parameter directionality: The directionality to assign. - func dir(_ directionality: Directionality) -> Component { + func directionality(_ directionality: Directionality) -> Component { attribute(named: "dir", value: directionality.rawValue) } From ec5b53acd57bd428fc593749cac9b0cea0e7a07c Mon Sep 17 00:00:00 2001 From: John Sundell Date: Fri, 31 Mar 2023 19:39:21 +0200 Subject: [PATCH 33/33] Update tests for new directionality API naming Also use the new, long-form name within the unit tests. --- Tests/PlotTests/HTMLComponentTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/PlotTests/HTMLComponentTests.swift b/Tests/PlotTests/HTMLComponentTests.swift index 4498bc3..911d11c 100644 --- a/Tests/PlotTests/HTMLComponentTests.swift +++ b/Tests/PlotTests/HTMLComponentTests.swift @@ -80,7 +80,7 @@ final class HTMLComponentTests: XCTestCase { func testAssigningDirectionalityToElement() { let html = Paragraph("Hello") - .dir(.leftToRight) + .directionality(.leftToRight) .render() XCTAssertEqual(html, #"

Hello

"#)