mirror of
https://github.com/gonzalezreal/swift-markdown-ui.git
synced 2026-01-18 17:41:20 +01:00
Add documentation
This commit is contained in:
@@ -7,11 +7,11 @@ struct AllExamplesView: View {
|
||||
private var content: some View {
|
||||
List {
|
||||
ForEach(examples) { example in
|
||||
Markdown(example.content)
|
||||
Markdown(example.document)
|
||||
.padding()
|
||||
.documentStyle(
|
||||
.markdownStyle(
|
||||
example.useDefaultStyle
|
||||
? DocumentStyle(font: .system(.body))
|
||||
? MarkdownStyle(font: .system(.body))
|
||||
: .alternative
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import CommonMark
|
||||
import Foundation
|
||||
import MarkdownUI
|
||||
|
||||
struct Example: Identifiable, Hashable {
|
||||
var id: String
|
||||
var title: String
|
||||
var content: String
|
||||
var document: Document
|
||||
var useDefaultStyle = true
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ extension Example {
|
||||
static let text = Example(
|
||||
id: "text",
|
||||
title: "Text",
|
||||
content: #"""
|
||||
document: #"""
|
||||
It's very easy to make some words **bold** and other words *italic* with Markdown.
|
||||
|
||||
**Want to experiment with Markdown?** Play with the [reference CommonMark
|
||||
@@ -26,7 +26,7 @@ extension Example {
|
||||
static let lists = Example(
|
||||
id: "lists",
|
||||
title: "Lists",
|
||||
content: #"""
|
||||
document: #"""
|
||||
Sometimes you want numbered lists:
|
||||
|
||||
1. One
|
||||
@@ -50,7 +50,7 @@ extension Example {
|
||||
static let images = Example(
|
||||
id: "images",
|
||||
title: "Images",
|
||||
content: #"""
|
||||
document: #"""
|
||||
If you want to embed images, this is how you do it:
|
||||
|
||||

|
||||
@@ -60,7 +60,7 @@ extension Example {
|
||||
static let headers = Example(
|
||||
id: "headers",
|
||||
title: "Headers & Quotes",
|
||||
content: #"""
|
||||
document: #"""
|
||||
# Structured documents
|
||||
|
||||
Sometimes it's useful to have different levels of headings to structure your documents. Start lines with a `#` to create headings. Multiple `##` in a row denote smaller heading sizes.
|
||||
@@ -79,7 +79,7 @@ extension Example {
|
||||
static let code = Example(
|
||||
id: "code",
|
||||
title: "Code",
|
||||
content: #"""
|
||||
document: #"""
|
||||
There are many different ways to style code with CommonMark. If you have inline code blocks, wrap them in backticks: `var example = true`. If you've got a longer block of code, you can indent with four spaces:
|
||||
|
||||
if isAwesome {
|
||||
@@ -99,15 +99,15 @@ extension Example {
|
||||
static let style = Example(
|
||||
id: "style",
|
||||
title: "Style",
|
||||
content: #"""
|
||||
document: #"""
|
||||
## Document Style
|
||||
By default, MardownUI renders markdown strings using the system fonts and reasonable defaults for paragraph spacing, indent size, heading size, etc.
|
||||
|
||||
If you don't want to use the default style, you can provide a custom `DocumentStyle` by using the `documentStyle()` modifier:
|
||||
If you don't want to use the default style, you can provide a custom `MarkdownStyle` by using the `markdownStyle()` modifier:
|
||||
|
||||
```
|
||||
.documentStyle(
|
||||
DocumentStyle(
|
||||
.markdownStyle(
|
||||
MarkdownStyle(
|
||||
font: .system(
|
||||
.body,
|
||||
design: .serif
|
||||
|
||||
@@ -8,7 +8,7 @@ struct ExampleView: View {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Text(example.content)
|
||||
Text(example.document.description)
|
||||
Spacer()
|
||||
}
|
||||
.font(.system(.callout, design: .monospaced))
|
||||
@@ -17,7 +17,7 @@ struct ExampleView: View {
|
||||
|
||||
Divider()
|
||||
|
||||
Markdown(example.content)
|
||||
Markdown(example.document)
|
||||
.padding()
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
@@ -28,7 +28,7 @@ struct ExampleView: View {
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle(example.title)
|
||||
.documentStyle(example.useDefaultStyle ? DocumentStyle(font: .system(.body)) : .alternative)
|
||||
.markdownStyle(example.useDefaultStyle ? MarkdownStyle(font: .system(.body)) : .alternative)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -40,9 +40,13 @@ struct ExampleView: View {
|
||||
}
|
||||
}
|
||||
|
||||
extension DocumentStyle {
|
||||
static var alternative: DocumentStyle {
|
||||
DocumentStyle(font: .system(.body, design: .serif), codeFontName: "Menlo")
|
||||
extension MarkdownStyle {
|
||||
static var alternative: MarkdownStyle {
|
||||
MarkdownStyle(
|
||||
font: .system(.body, design: .serif),
|
||||
codeFontName: "Menlo",
|
||||
codeFontSize: .em(0.88)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
LICENSE
Normal file
22
LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Guillermo Gonzalez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,17 +1,35 @@
|
||||
import cmark
|
||||
import Foundation
|
||||
|
||||
/// A CommonMark document.
|
||||
public struct Document {
|
||||
/// A CommonMark document inline.
|
||||
public enum Inline: Equatable {
|
||||
/// Plain textual content.
|
||||
case text(String)
|
||||
|
||||
/// Soft line break.
|
||||
case softBreak
|
||||
|
||||
/// Hard line break.
|
||||
case lineBreak
|
||||
|
||||
/// Code span.
|
||||
case code(String)
|
||||
|
||||
/// Raw HTML.
|
||||
case html(String)
|
||||
case custom(String)
|
||||
|
||||
/// Emphasis.
|
||||
case emphasis([Inline])
|
||||
|
||||
/// Strong emphasis.
|
||||
case strong([Inline])
|
||||
|
||||
/// Link.
|
||||
case link([Inline], url: String, title: String = "")
|
||||
|
||||
/// Image.
|
||||
case image([Inline], url: String, title: String = "")
|
||||
|
||||
init(node: Node) {
|
||||
@@ -26,8 +44,6 @@ public struct Document {
|
||||
self = .code(node.literal!)
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
self = .html(node.literal!)
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
self = .custom(node.literal!)
|
||||
case CMARK_NODE_EMPH:
|
||||
self = .emphasis(node.children.map(Inline.init))
|
||||
case CMARK_NODE_STRONG:
|
||||
@@ -42,7 +58,9 @@ public struct Document {
|
||||
}
|
||||
}
|
||||
|
||||
/// A CommonMark list.
|
||||
public struct List: Equatable {
|
||||
/// List style.
|
||||
public enum Style: Equatable {
|
||||
case bullet, ordered
|
||||
|
||||
@@ -56,6 +74,7 @@ public struct Document {
|
||||
}
|
||||
}
|
||||
|
||||
/// A single list item.
|
||||
public struct Item: Equatable {
|
||||
public let blocks: [Block]
|
||||
|
||||
@@ -69,9 +88,16 @@ public struct Document {
|
||||
}
|
||||
}
|
||||
|
||||
/// The items in this list.
|
||||
public let items: [Item]
|
||||
|
||||
/// The list style.
|
||||
public let style: Style
|
||||
|
||||
/// The start index in an ordered list.
|
||||
public let start: Int
|
||||
|
||||
/// Whether or not this list has tight or loose spacing between its items.
|
||||
public let isTight: Bool
|
||||
|
||||
public init(items: [Item], style: Style, start: Int, isTight: Bool) {
|
||||
@@ -91,14 +117,27 @@ public struct Document {
|
||||
}
|
||||
}
|
||||
|
||||
/// A CommonMark document block.
|
||||
public enum Block: Equatable {
|
||||
/// A block quote.
|
||||
case blockQuote([Block])
|
||||
|
||||
/// A list.
|
||||
case list(List)
|
||||
|
||||
/// A code block.
|
||||
case code(String, language: String = "")
|
||||
|
||||
/// A group of lines that is treated as raw HTML.
|
||||
case html(String)
|
||||
case custom(String)
|
||||
|
||||
/// A paragraph.
|
||||
case paragraph([Inline])
|
||||
|
||||
/// A heading.
|
||||
case heading([Inline], level: Int)
|
||||
|
||||
/// A thematic break.
|
||||
case thematicBreak
|
||||
|
||||
init(node: Node) {
|
||||
@@ -111,8 +150,6 @@ public struct Document {
|
||||
self = .code(node.literal!, language: node.fenceInfo ?? "")
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
self = .html(node.literal!)
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
self = .custom(node.literal!)
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
self = .paragraph(node.children.map(Inline.init))
|
||||
case CMARK_NODE_HEADING:
|
||||
@@ -128,15 +165,21 @@ public struct Document {
|
||||
}
|
||||
}
|
||||
|
||||
/// The blocks that form this document.
|
||||
public var blocks: [Block] {
|
||||
node.children.map(Block.init)
|
||||
}
|
||||
|
||||
/// A set with all the image locations contained in this document.
|
||||
public var imageURLs: Set<String> {
|
||||
Set(node.imageURLs)
|
||||
}
|
||||
|
||||
private let node: Node
|
||||
|
||||
public init(_ content: String) {
|
||||
node = Node(content)!
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: Equatable {
|
||||
@@ -155,28 +198,27 @@ extension Document: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: LosslessStringConvertible {
|
||||
public init?(_ description: String) {
|
||||
guard let node = Node(description) else {
|
||||
return nil
|
||||
}
|
||||
self.node = node
|
||||
}
|
||||
|
||||
extension Document: CustomStringConvertible {
|
||||
public var description: String {
|
||||
node.description
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: ExpressibleByStringInterpolation {
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self.init(value)
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: Codable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let description = try container.decode(String.self)
|
||||
let content = try container.decode(String.self)
|
||||
|
||||
guard let node = Node(description) else {
|
||||
guard let node = Node(content) else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Invalid document: \(description)"
|
||||
debugDescription: "Invalid document: \(content)"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class Node {
|
||||
}
|
||||
|
||||
convenience init?(_ cmark: String) {
|
||||
guard let node = cmark_parse_document(cmark, cmark.utf8.count, 0) else {
|
||||
guard let node = cmark_parse_document(cmark, cmark.utf8.count, CMARK_OPT_SMART) else {
|
||||
return nil
|
||||
}
|
||||
self.init(node)
|
||||
|
||||
@@ -3,22 +3,26 @@
|
||||
import AppKit
|
||||
|
||||
public extension NSFont {
|
||||
/// Create a system font by specifying the size and weight.
|
||||
static func system(size: CGFloat, weight: Weight = .regular) -> NSFont {
|
||||
.systemFont(ofSize: size, weight: weight)
|
||||
}
|
||||
|
||||
/// Create a system font by specifying the size, weight, and a type design together.
|
||||
@available(macOS 10.15, *)
|
||||
static func system(size: CGFloat, weight: Weight = .regular, design: NSFontDescriptor.SystemDesign) -> NSFont {
|
||||
let font = NSFont.systemFont(ofSize: size, weight: weight)
|
||||
return font.withDesign(design) ?? font
|
||||
}
|
||||
|
||||
/// Create a system font with the given style and design.
|
||||
@available(macOS 11.0, *)
|
||||
static func system(_ style: TextStyle, design: NSFontDescriptor.SystemDesign = .default) -> NSFont {
|
||||
let font = NSFont.preferredFont(forTextStyle: style)
|
||||
return font.withDesign(design) ?? font
|
||||
}
|
||||
|
||||
/// Create a custom font with the given name and a fixed size.
|
||||
static func custom(_ name: String, size: CGFloat) -> NSFont {
|
||||
NSFont(name: name, size: size) ?? .system(size: size)
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
/// A quantity that can be expressed in points or relative to the font size.
|
||||
public enum Dimension: Equatable {
|
||||
/// Absolute points.
|
||||
case points(CGFloat)
|
||||
|
||||
/// Relative to the font-size (.em(2) means 2 times the size of the current font)
|
||||
case em(CGFloat)
|
||||
|
||||
var points: CGFloat? {
|
||||
public var points: CGFloat? {
|
||||
guard case let .points(value) = self else { return nil }
|
||||
return value
|
||||
}
|
||||
|
||||
var em: CGFloat? {
|
||||
public var em: CGFloat? {
|
||||
guard case let .em(value) = self else { return nil }
|
||||
return value
|
||||
}
|
||||
|
||||
func resolve(_ parentSize: CGFloat) -> CGFloat {
|
||||
public func resolve(_ parentSize: CGFloat) -> CGFloat {
|
||||
switch self {
|
||||
case let .points(value):
|
||||
return value
|
||||
|
||||
@@ -8,11 +8,20 @@ import CommonMark
|
||||
|
||||
public extension NSAttributedString {
|
||||
#if !os(watchOS)
|
||||
convenience init(document: Document, attachments: [String: NSTextAttachment] = [:], style: DocumentStyle) {
|
||||
/// Create an attributed string from a CommonMark document.
|
||||
/// - Parameters:
|
||||
/// - document: A CommonMark document.
|
||||
/// - attachments: A dictionary of text attachments, keyed by the URL strings corresponding to images in the CommonMark document.
|
||||
/// - style: A document style describing how the document is going to be rendered.
|
||||
convenience init(document: Document, attachments: [String: NSTextAttachment] = [:], style: MarkdownStyle) {
|
||||
self.init(attributedString: document.attributedString(attachments: attachments, style: style))
|
||||
}
|
||||
#else
|
||||
convenience init(document: Document, style: DocumentStyle) {
|
||||
/// Create an attributed string from a CommonMark document.
|
||||
/// - Parameters:
|
||||
/// - document: A CommonMark document.
|
||||
/// - style: A document style describing how the document is going to be rendered.
|
||||
convenience init(document: Document, style: MarkdownStyle) {
|
||||
self.init(attributedString: document.attributedString(style: style))
|
||||
}
|
||||
#endif
|
||||
@@ -20,11 +29,18 @@ public extension NSAttributedString {
|
||||
|
||||
public extension Document {
|
||||
#if !os(watchOS)
|
||||
func attributedString(attachments: [String: NSTextAttachment] = [:], style: DocumentStyle) -> NSAttributedString {
|
||||
/// Create an attributed string from this document.
|
||||
/// - Parameters:
|
||||
/// - attachments: A dictionary of text attachments, keyed by the URL strings corresponding to images in this document.
|
||||
/// - style: A document style describing how the document is going to be rendered.
|
||||
func attributedString(attachments: [String: NSTextAttachment] = [:], style: MarkdownStyle) -> NSAttributedString {
|
||||
blocks.attributedString(context: RenderContext(attachments: attachments, style: style))
|
||||
}
|
||||
#else
|
||||
func attributedString(style: DocumentStyle) -> NSAttributedString {
|
||||
/// Create an attributed string from this document.
|
||||
/// - Parameters:
|
||||
/// - style: A document style describing how the document is going to be rendered.
|
||||
func attributedString(style: MarkdownStyle) -> NSAttributedString {
|
||||
blocks.attributedString(context: RenderContext(style: style))
|
||||
}
|
||||
#endif
|
||||
@@ -49,8 +65,6 @@ extension Document.Inline {
|
||||
return NSAttributedString(string: text, attributes: context.code().attributes)
|
||||
case let .html(text):
|
||||
return NSAttributedString(string: text, attributes: context.attributes)
|
||||
case let .custom(text):
|
||||
return NSAttributedString(string: text, attributes: context.attributes)
|
||||
case let .emphasis(inlines):
|
||||
return inlines.attributedString(context: context.emphasis())
|
||||
case let .strong(inlines):
|
||||
@@ -115,8 +129,6 @@ extension Document.Block {
|
||||
return result
|
||||
}
|
||||
|
||||
return NSAttributedString()
|
||||
case .custom:
|
||||
return NSAttributedString()
|
||||
case let .paragraph(inlines):
|
||||
return inlines.attributedString(context: context.paragraph())
|
||||
|
||||
@@ -4,10 +4,18 @@
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
/// A set of values that control the appearance of headings in Markdown views.
|
||||
public struct HeadingStyle: Equatable {
|
||||
/// The font size of this heading.
|
||||
public var fontSize: Dimension
|
||||
|
||||
/// The text alignment of this heading.
|
||||
public var alignment: NSTextAlignment
|
||||
|
||||
/// The space after this heading.
|
||||
public var spacing: Dimension
|
||||
|
||||
/// Whether or not this heading uses a bold font.
|
||||
public var isBold: Bool
|
||||
|
||||
public init(
|
||||
|
||||
@@ -4,25 +4,26 @@
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
|
||||
public extension View {
|
||||
/// Sets the markdown style in this view and its children.
|
||||
@available(macOS 11.0, *)
|
||||
func documentStyle(_ documentStyle: @autoclosure @escaping () -> DocumentStyle) -> some View {
|
||||
environment(\.documentStyle, documentStyle)
|
||||
func markdownStyle(_ markdownStyle: @autoclosure @escaping () -> MarkdownStyle) -> some View {
|
||||
environment(\.markdownStyle, markdownStyle)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
|
||||
extension EnvironmentValues {
|
||||
@available(macOS 11.0, *)
|
||||
var documentStyle: () -> DocumentStyle {
|
||||
get { self[DocumentStyleKey.self] }
|
||||
set { self[DocumentStyleKey.self] = newValue }
|
||||
var markdownStyle: () -> MarkdownStyle {
|
||||
get { self[MarkdownStyleKey.self] }
|
||||
set { self[MarkdownStyleKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 11.0, iOS 13.0, tvOS 13.0, *)
|
||||
private struct DocumentStyleKey: EnvironmentKey {
|
||||
static let defaultValue: () -> DocumentStyle = {
|
||||
DocumentStyle(font: .system(.body))
|
||||
private struct MarkdownStyleKey: EnvironmentKey {
|
||||
static let defaultValue: () -> MarkdownStyle = {
|
||||
MarkdownStyle(font: .system(.body))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,30 +5,61 @@
|
||||
import NetworkImage
|
||||
import SwiftUI
|
||||
|
||||
/// A view that displays Markdown formatted text.
|
||||
///
|
||||
/// A Markdown view displays formatted text using the Markdown syntax, fully compliant
|
||||
/// with the [CommonMark Spec](https://spec.commonmark.org/current/).
|
||||
///
|
||||
/// Markdown("It's very easy to make some words **bold** and other words *italic* with Markdown.")
|
||||
///
|
||||
/// A Markdown view renders text using a `body` font appropriate for the current platform.
|
||||
/// You can choose a different font or customize other properties like the foreground color,
|
||||
/// paragraph spacing, or heading styles using the `markdownStyle(_:)` view modifier.
|
||||
///
|
||||
/// Markdown("If you have inline code blocks, wrap them in backticks: `var example = true`.")
|
||||
/// .markdownStyle(
|
||||
/// MarkdownStyle(
|
||||
/// font: .custom("Helvetica Neue", size: 14),
|
||||
/// foregroundColor: .gray
|
||||
/// codeFontName: "Menlo"
|
||||
/// )
|
||||
/// )
|
||||
///
|
||||
/// Use the `accentColor(_:)` view modifier to configure the link color.
|
||||
///
|
||||
/// Markdown("Play with the [reference CommonMark implementation](https://spec.commonmark.org/dingus/).")
|
||||
/// .accentColor(.purple)
|
||||
///
|
||||
/// A Markdown view always uses all the available width and adjusts its height to fit its
|
||||
/// rendered text.
|
||||
///
|
||||
/// Use modifiers like `lineLimit(_:)` and `truncationMode(_:)` to configure
|
||||
/// how the view handles space constraints.
|
||||
///
|
||||
/// Markdown("> Knowledge is power, Francis Bacon.")
|
||||
/// .lineLimit(1)
|
||||
@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)
|
||||
public struct Markdown: View {
|
||||
@Environment(\.sizeCategory) private var sizeCategory: ContentSizeCategory
|
||||
@Environment(\.documentStyle) private var documentStyle: () -> DocumentStyle
|
||||
@Environment(\.markdownStyle) private var markdownStyle: () -> MarkdownStyle
|
||||
|
||||
@StateObject private var store = MarkdownStore()
|
||||
|
||||
private let document: Document?
|
||||
private let document: Document
|
||||
|
||||
public init(_ content: String) {
|
||||
self.init(document: Document(content))
|
||||
}
|
||||
|
||||
public init(document: Document?) {
|
||||
/// Creates a Markdown view that displays a CommonMark document.
|
||||
/// - Parameter document: The CommonMark document to display.
|
||||
public init(_ document: Document) {
|
||||
self.document = document
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
AttributedText(store.attributedText)
|
||||
.onChange(of: sizeCategory) { _ in
|
||||
store.setDocument(document, style: documentStyle())
|
||||
store.setDocument(document, style: markdownStyle())
|
||||
}
|
||||
.onAppear {
|
||||
store.setDocument(document, style: documentStyle())
|
||||
store.setDocument(document, style: markdownStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
@Published private(set) var attributedText = NSAttributedString()
|
||||
|
||||
private var document: Document?
|
||||
private var style: DocumentStyle?
|
||||
private var style: MarkdownStyle?
|
||||
private var cancellable: AnyCancellable?
|
||||
|
||||
func setDocument(_ document: Document?, style: DocumentStyle) {
|
||||
func setDocument(_ document: Document?, style: MarkdownStyle) {
|
||||
guard self.document != document || self.style != style else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,91 +4,7 @@
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
public struct DocumentStyle: Equatable {
|
||||
#if os(macOS)
|
||||
public typealias Font = NSFont
|
||||
public typealias Color = NSColor
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
public typealias Font = UIFont
|
||||
public typealias Color = UIColor
|
||||
#endif
|
||||
|
||||
public var font: Font
|
||||
public var foregroundColor: Color
|
||||
public var alignment: NSTextAlignment
|
||||
public var lineHeight: Dimension
|
||||
public var paragraphSpacing: Dimension
|
||||
public var indentSize: Dimension
|
||||
public var codeFontName: String?
|
||||
public var codeFontSize: Dimension
|
||||
public var headingStyles: [HeadingStyle]
|
||||
public var thematicBreakStyle: ThematicBreakStyle
|
||||
|
||||
public init(
|
||||
font: Font,
|
||||
foregroundColor: Color,
|
||||
alignment: NSTextAlignment = .natural,
|
||||
lineHeight: Dimension = .em(1),
|
||||
paragraphSpacing: Dimension = .em(1),
|
||||
indentSize: Dimension = .em(1),
|
||||
codeFontName: String? = nil,
|
||||
codeFontSize: Dimension = .em(0.88),
|
||||
headingStyles: [HeadingStyle] = HeadingStyle.default,
|
||||
thematicBreakStyle: ThematicBreakStyle = .default
|
||||
) {
|
||||
self.font = font
|
||||
self.foregroundColor = foregroundColor
|
||||
self.alignment = alignment
|
||||
self.lineHeight = lineHeight
|
||||
self.paragraphSpacing = paragraphSpacing
|
||||
self.indentSize = indentSize
|
||||
self.codeFontName = codeFontName
|
||||
self.codeFontSize = codeFontSize
|
||||
self.thematicBreakStyle = thematicBreakStyle
|
||||
self.headingStyles = headingStyles
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 11.0, iOS 13.0, tvOS 13.0, *)
|
||||
public extension DocumentStyle {
|
||||
@available(watchOS, unavailable)
|
||||
init(
|
||||
font: Font,
|
||||
alignment: NSTextAlignment = .natural,
|
||||
lineHeight: Dimension = .em(1),
|
||||
paragraphSpacing: Dimension = .em(1),
|
||||
indentSize: Dimension = .em(1),
|
||||
codeFontName: String? = nil,
|
||||
codeFontSize: Dimension = .em(0.88),
|
||||
headingStyles: [HeadingStyle] = HeadingStyle.default,
|
||||
thematicBreakStyle: ThematicBreakStyle = .default
|
||||
) {
|
||||
#if os(watchOS)
|
||||
fatalError("unavailable!")
|
||||
#else
|
||||
#if os(macOS)
|
||||
let foregroundColor = NSColor.labelColor
|
||||
#else
|
||||
let foregroundColor = UIColor.label
|
||||
#endif
|
||||
|
||||
self.init(
|
||||
font: font,
|
||||
foregroundColor: foregroundColor,
|
||||
alignment: alignment,
|
||||
lineHeight: lineHeight,
|
||||
paragraphSpacing: paragraphSpacing,
|
||||
indentSize: indentSize,
|
||||
codeFontName: codeFontName,
|
||||
codeFontSize: codeFontSize,
|
||||
headingStyles: headingStyles,
|
||||
thematicBreakStyle: thematicBreakStyle
|
||||
)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extension DocumentStyle {
|
||||
extension MarkdownStyle {
|
||||
func paragraphStyle(indentLevel: Int, options: ParagraphOptions) -> NSParagraphStyle {
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
|
||||
109
Sources/MarkdownUI/Shared/MarkdownStyle.swift
Normal file
109
Sources/MarkdownUI/Shared/MarkdownStyle.swift
Normal file
@@ -0,0 +1,109 @@
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#elseif canImport(UIKit)
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
/// A set of values that control the appearance of Markdown views.
|
||||
public struct MarkdownStyle: Equatable {
|
||||
#if os(macOS)
|
||||
public typealias Font = NSFont
|
||||
public typealias Color = NSColor
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
public typealias Font = UIFont
|
||||
public typealias Color = UIColor
|
||||
#endif
|
||||
|
||||
/// The base font used to render the document.
|
||||
public var font: Font
|
||||
|
||||
/// The text color.
|
||||
public var foregroundColor: Color
|
||||
|
||||
/// The text alignment.
|
||||
public var alignment: NSTextAlignment
|
||||
|
||||
/// The line height.
|
||||
public var lineHeight: Dimension
|
||||
|
||||
/// The space after the end of the paragraph.
|
||||
public var paragraphSpacing: Dimension
|
||||
|
||||
/// The indent size for quotes, lists and code blocks.
|
||||
public var indentSize: Dimension
|
||||
|
||||
/// The code font name. If `nil` the system monospaced font is used.
|
||||
public var codeFontName: String?
|
||||
|
||||
/// The code font size.
|
||||
public var codeFontSize: Dimension
|
||||
|
||||
/// The heading styles.
|
||||
public var headingStyles: [HeadingStyle]
|
||||
|
||||
/// The thematic break style.
|
||||
public var thematicBreakStyle: ThematicBreakStyle
|
||||
|
||||
public init(
|
||||
font: Font,
|
||||
foregroundColor: Color,
|
||||
alignment: NSTextAlignment = .natural,
|
||||
lineHeight: Dimension = .em(1),
|
||||
paragraphSpacing: Dimension = .em(1),
|
||||
indentSize: Dimension = .em(1),
|
||||
codeFontName: String? = nil,
|
||||
codeFontSize: Dimension = .em(0.94),
|
||||
headingStyles: [HeadingStyle] = HeadingStyle.default,
|
||||
thematicBreakStyle: ThematicBreakStyle = .default
|
||||
) {
|
||||
self.font = font
|
||||
self.foregroundColor = foregroundColor
|
||||
self.alignment = alignment
|
||||
self.lineHeight = lineHeight
|
||||
self.paragraphSpacing = paragraphSpacing
|
||||
self.indentSize = indentSize
|
||||
self.codeFontName = codeFontName
|
||||
self.codeFontSize = codeFontSize
|
||||
self.thematicBreakStyle = thematicBreakStyle
|
||||
self.headingStyles = headingStyles
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 11.0, iOS 13.0, tvOS 13.0, *)
|
||||
public extension MarkdownStyle {
|
||||
@available(watchOS, unavailable)
|
||||
init(
|
||||
font: Font,
|
||||
alignment: NSTextAlignment = .natural,
|
||||
lineHeight: Dimension = .em(1),
|
||||
paragraphSpacing: Dimension = .em(1),
|
||||
indentSize: Dimension = .em(1),
|
||||
codeFontName: String? = nil,
|
||||
codeFontSize: Dimension = .em(0.94),
|
||||
headingStyles: [HeadingStyle] = HeadingStyle.default,
|
||||
thematicBreakStyle: ThematicBreakStyle = .default
|
||||
) {
|
||||
#if os(watchOS)
|
||||
fatalError("unavailable!")
|
||||
#else
|
||||
#if os(macOS)
|
||||
let foregroundColor = NSColor.labelColor
|
||||
#else
|
||||
let foregroundColor = UIColor.label
|
||||
#endif
|
||||
|
||||
self.init(
|
||||
font: font,
|
||||
foregroundColor: foregroundColor,
|
||||
alignment: alignment,
|
||||
lineHeight: lineHeight,
|
||||
paragraphSpacing: paragraphSpacing,
|
||||
indentSize: indentSize,
|
||||
codeFontName: codeFontName,
|
||||
codeFontSize: codeFontSize,
|
||||
headingStyles: headingStyles,
|
||||
thematicBreakStyle: thematicBreakStyle
|
||||
)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,17 @@ struct RenderContext {
|
||||
let attachments: [String: NSTextAttachment]
|
||||
#endif
|
||||
|
||||
let style: DocumentStyle
|
||||
let style: MarkdownStyle
|
||||
private var paragraphOptions: ParagraphOptions = []
|
||||
private var indentLevel = 0
|
||||
|
||||
private var currentFont: DocumentStyle.Font? {
|
||||
get { attributes[.font] as? DocumentStyle.Font }
|
||||
private var currentFont: MarkdownStyle.Font? {
|
||||
get { attributes[.font] as? MarkdownStyle.Font }
|
||||
set { attributes[.font] = newValue }
|
||||
}
|
||||
|
||||
#if !os(watchOS)
|
||||
init(attachments: [String: NSTextAttachment], style: DocumentStyle) {
|
||||
init(attachments: [String: NSTextAttachment], style: MarkdownStyle) {
|
||||
self.attachments = attachments
|
||||
self.style = style
|
||||
|
||||
@@ -35,7 +35,7 @@ struct RenderContext {
|
||||
attachments[url]
|
||||
}
|
||||
#else
|
||||
init(style: DocumentStyle) {
|
||||
init(style: MarkdownStyle) {
|
||||
self.style = style
|
||||
attributes = [.font: style.font]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
/// A set of values that control the appearance of thematic breaks in Markdown views.
|
||||
public struct ThematicBreakStyle: Equatable {
|
||||
public var text: String
|
||||
public var alignment: NSTextAlignment
|
||||
|
||||
@@ -3,26 +3,31 @@
|
||||
import UIKit
|
||||
|
||||
public extension UIFont {
|
||||
/// Create a system font by specifying the size and weight.
|
||||
static func system(size: CGFloat, weight: Weight = .regular) -> UIFont {
|
||||
.systemFont(ofSize: size, weight: weight)
|
||||
}
|
||||
|
||||
/// Create a system font by specifying the size, weight, and a type design together.
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 7.0, *)
|
||||
static func system(size: CGFloat, weight: Weight = .regular, design: UIFontDescriptor.SystemDesign) -> UIFont {
|
||||
let font = UIFont.systemFont(ofSize: size, weight: weight)
|
||||
return font.withDesign(design) ?? font
|
||||
}
|
||||
|
||||
/// Create a system font with the given style.
|
||||
static func system(_ style: TextStyle) -> UIFont {
|
||||
.preferredFont(forTextStyle: style)
|
||||
}
|
||||
|
||||
/// Create a system font with the given style and design.
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 7.0, *)
|
||||
static func system(_ style: TextStyle, design: UIFontDescriptor.SystemDesign) -> UIFont {
|
||||
let font = UIFont.preferredFont(forTextStyle: style)
|
||||
return font.withDesign(design) ?? font
|
||||
}
|
||||
|
||||
/// Create a custom font with the given name and size that scales with the body text style.
|
||||
@available(watchOS 4.0, *)
|
||||
static func custom(_ name: String, size: CGFloat) -> UIFont {
|
||||
guard let customFont = UIFont(name: name, size: size) else {
|
||||
|
||||
@@ -21,7 +21,19 @@ final class DocumentTests: XCTestCase {
|
||||
let expected = "# **Hello** *world*\n"
|
||||
|
||||
// when
|
||||
let result = Document(text)?.description
|
||||
let result = Document(text).description
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
}
|
||||
|
||||
func testEmptyDocument() {
|
||||
// given
|
||||
let content = ""
|
||||
let expected: [Document.Block] = []
|
||||
|
||||
// when
|
||||
let result = Document(content).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -43,7 +55,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -93,7 +105,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -144,7 +156,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -166,7 +178,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -180,7 +192,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -194,7 +206,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -212,7 +224,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -233,7 +245,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -251,7 +263,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -269,7 +281,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -289,7 +301,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -307,7 +319,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -325,7 +337,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -343,7 +355,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -361,7 +373,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.blocks
|
||||
let result = Document(text).blocks
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
@@ -384,7 +396,7 @@ final class DocumentTests: XCTestCase {
|
||||
]
|
||||
|
||||
// when
|
||||
let result = Document(text)?.imageURLs
|
||||
let result = Document(text).imageURLs
|
||||
|
||||
// then
|
||||
XCTAssertEqual(result, expected)
|
||||
|
||||
@@ -11,7 +11,7 @@ import XCTest
|
||||
import MarkdownUI
|
||||
|
||||
final class NSAttributedStringTests: XCTestCase {
|
||||
private let style = DocumentStyle(
|
||||
private let style = MarkdownStyle(
|
||||
font: .custom("Helvetica Neue", size: 17),
|
||||
foregroundColor: .black,
|
||||
lineHeight: .em(1),
|
||||
@@ -32,13 +32,11 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
#endif
|
||||
|
||||
func testParagraph() {
|
||||
let document = Document(
|
||||
#"""
|
||||
The sky above the port was the color of television, tuned to a dead channel.
|
||||
let document: Document = #"""
|
||||
The sky above the port was the color of television, tuned to a dead channel.
|
||||
|
||||
It was a bright cold day in April, and the clocks were striking thirteen.
|
||||
"""#
|
||||
)!
|
||||
It was a bright cold day in April, and the clocks were striking thirteen.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -46,17 +44,15 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRightAlignedParagraph() {
|
||||
let document = Document(
|
||||
#"""
|
||||
The sky above the port was the color of television, tuned to a dead channel.
|
||||
let document: Document = #"""
|
||||
The sky above the port was the color of television, tuned to a dead channel.
|
||||
|
||||
It was a bright cold day in April, and the clocks were striking thirteen.
|
||||
"""#
|
||||
)!
|
||||
It was a bright cold day in April, and the clocks were striking thirteen.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(
|
||||
document: document,
|
||||
style: DocumentStyle(
|
||||
style: MarkdownStyle(
|
||||
font: .custom("Helvetica Neue", size: 17),
|
||||
foregroundColor: .black,
|
||||
alignment: .right
|
||||
@@ -67,14 +63,12 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testParagraphAndLineBreak() {
|
||||
let document = Document(
|
||||
#"""
|
||||
The sky above the port was the color of television,\
|
||||
tuned to a dead channel.
|
||||
let document: Document = #"""
|
||||
The sky above the port was the color of television,\
|
||||
tuned to a dead channel.
|
||||
|
||||
It was a bright cold day in April, and the clocks were striking thirteen.
|
||||
"""#
|
||||
)!
|
||||
It was a bright cold day in April, and the clocks were striking thirteen.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -82,15 +76,13 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testBlockQuote() {
|
||||
let document = Document(
|
||||
#"""
|
||||
The quote
|
||||
let document: Document = #"""
|
||||
The quote
|
||||
|
||||
> Somewhere, something incredible is waiting to be known
|
||||
> Somewhere, something incredible is waiting to be known
|
||||
|
||||
has been ascribed to Carl Sagan.
|
||||
"""#
|
||||
)!
|
||||
has been ascribed to Carl Sagan.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -98,18 +90,16 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testNestedBlockQuote() {
|
||||
let document = Document(
|
||||
#"""
|
||||
Blockquotes can be nested, and can also contain other formatting:
|
||||
let document: Document = #"""
|
||||
Blockquotes can be nested, and can also contain other formatting:
|
||||
|
||||
> Lorem ipsum dolor sit amet,
|
||||
> consectetur adipiscing elit.
|
||||
>
|
||||
> > Ut enim ad minim **veniam**.
|
||||
>
|
||||
> Excepteur sint occaecat cupidatat non proident.
|
||||
"""#
|
||||
)!
|
||||
> Lorem ipsum dolor sit amet,
|
||||
> consectetur adipiscing elit.
|
||||
>
|
||||
> > Ut enim ad minim **veniam**.
|
||||
>
|
||||
> Excepteur sint occaecat cupidatat non proident.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -117,30 +107,28 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testMultipleParagraphList() {
|
||||
let document = Document(
|
||||
#"""
|
||||
Before
|
||||
1. List item one.
|
||||
let document: Document = #"""
|
||||
Before
|
||||
1. List item one.
|
||||
|
||||
List item one continued with a second paragraph.
|
||||
List item one continued with a second paragraph.
|
||||
|
||||
List item continued with a third paragraph.
|
||||
List item continued with a third paragraph.
|
||||
|
||||
1. List item two continued with an open block.
|
||||
1. List item two continued with an open block.
|
||||
|
||||
This paragraph is part of the preceding list item.
|
||||
This paragraph is part of the preceding list item.
|
||||
|
||||
1. This list is nested and does not require explicit item continuation.
|
||||
1. This list is nested and does not require explicit item continuation.
|
||||
|
||||
This paragraph is part of the preceding list item.
|
||||
This paragraph is part of the preceding list item.
|
||||
|
||||
1. List item b.
|
||||
1. List item b.
|
||||
|
||||
This paragraph belongs to item two of the outer list.
|
||||
This paragraph belongs to item two of the outer list.
|
||||
|
||||
After
|
||||
"""#
|
||||
)!
|
||||
After
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -148,21 +136,19 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testTightList() {
|
||||
let document = Document(
|
||||
#"""
|
||||
The following sites and projects have adopted CommonMark:
|
||||
let document: Document = #"""
|
||||
The following sites and projects have adopted CommonMark:
|
||||
|
||||
* Discourse
|
||||
* GitHub
|
||||
* GitLab
|
||||
* Reddit
|
||||
* Qt
|
||||
* Stack Overflow / Stack Exchange
|
||||
* Swift
|
||||
* Discourse
|
||||
* GitHub
|
||||
* GitLab
|
||||
* Reddit
|
||||
* Qt
|
||||
* Stack Overflow / Stack Exchange
|
||||
* Swift
|
||||
|
||||
❤️
|
||||
"""#
|
||||
)!
|
||||
❤️
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -170,18 +156,16 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testLooseList() {
|
||||
let document = Document(
|
||||
#"""
|
||||
Before
|
||||
1. **one**
|
||||
- a
|
||||
let document: Document = #"""
|
||||
Before
|
||||
1. **one**
|
||||
- a
|
||||
|
||||
- b
|
||||
1. two
|
||||
- b
|
||||
1. two
|
||||
|
||||
After
|
||||
"""#
|
||||
)!
|
||||
After
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -189,22 +173,20 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testFencedCodeBlock() {
|
||||
let document = Document(
|
||||
#"""
|
||||
Create arrays and dictionaries using brackets (`[]`), and access their elements by writing the index or key in brackets. A comma is allowed after the last element.
|
||||
```
|
||||
var shoppingList = ["catfish", "water", "tulips"]
|
||||
shoppingList[1] = "bottle of water"
|
||||
let document: Document = #"""
|
||||
Create arrays and dictionaries using brackets (`[]`), and access their elements by writing the index or key in brackets. A comma is allowed after the last element.
|
||||
```
|
||||
var shoppingList = ["catfish", "water", "tulips"]
|
||||
shoppingList[1] = "bottle of water"
|
||||
|
||||
var occupations = [
|
||||
"Malcolm": "Captain",
|
||||
"Kaylee": "Mechanic",
|
||||
]
|
||||
occupations["Jayne"] = "Public Relations"
|
||||
```
|
||||
Arrays automatically grow as you add elements.
|
||||
"""#
|
||||
)!
|
||||
var occupations = [
|
||||
"Malcolm": "Captain",
|
||||
"Kaylee": "Mechanic",
|
||||
]
|
||||
occupations["Jayne"] = "Public Relations"
|
||||
```
|
||||
Arrays automatically grow as you add elements.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -212,19 +194,17 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testHTMLBlock() {
|
||||
let document = Document(
|
||||
#"""
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
hi
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
let document: Document = #"""
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
hi
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
okay.
|
||||
"""#
|
||||
)!
|
||||
okay.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -232,16 +212,14 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testHeadings() {
|
||||
let document = Document(
|
||||
#"""
|
||||
# After the Big Bang
|
||||
A brief summary of time
|
||||
## Life on earth
|
||||
10 billion years
|
||||
## You reading this
|
||||
13.7 billion years
|
||||
"""#
|
||||
)!
|
||||
let document: Document = #"""
|
||||
# After the Big Bang
|
||||
A brief summary of time
|
||||
## Life on earth
|
||||
10 billion years
|
||||
## You reading this
|
||||
13.7 billion years
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -249,13 +227,11 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testHeadingInsideList() {
|
||||
let document = Document(
|
||||
#"""
|
||||
1. # After the Big Bang
|
||||
1. ## Life on earth
|
||||
1. ### You reading this
|
||||
"""#
|
||||
)!
|
||||
let document: Document = #"""
|
||||
1. # After the Big Bang
|
||||
1. ## Life on earth
|
||||
1. ### You reading this
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -263,25 +239,23 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testThematicBreak() {
|
||||
let document = Document(
|
||||
#"""
|
||||
HTML is the standard markup language for creating Web pages. HTML describes
|
||||
the structure of a Web page, and consists of a series of elements.
|
||||
HTML elements tell the browser how to display the content.
|
||||
let document: Document = #"""
|
||||
HTML is the standard markup language for creating Web pages. HTML describes
|
||||
the structure of a Web page, and consists of a series of elements.
|
||||
HTML elements tell the browser how to display the content.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
CSS is a language that describes how HTML elements are to be displayed on
|
||||
screen, paper, or in other media. CSS saves a lot of work, because it can
|
||||
control the layout of multiple web pages all at once.
|
||||
CSS is a language that describes how HTML elements are to be displayed on
|
||||
screen, paper, or in other media. CSS saves a lot of work, because it can
|
||||
control the layout of multiple web pages all at once.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
JavaScript is the programming language of HTML and the Web. JavaScript can
|
||||
change HTML content and attribute values. JavaScript can change CSS.
|
||||
JavaScript can hide and show HTML elements, and more.
|
||||
"""#
|
||||
)!
|
||||
JavaScript is the programming language of HTML and the Web. JavaScript can
|
||||
change HTML content and attribute values. JavaScript can change CSS.
|
||||
JavaScript can hide and show HTML elements, and more.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -289,15 +263,13 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInlineHTML() {
|
||||
let document = Document(
|
||||
#"""
|
||||
Before
|
||||
let document: Document = #"""
|
||||
Before
|
||||
|
||||
<a><bab><c2c>
|
||||
<a><bab><c2c>
|
||||
|
||||
After
|
||||
"""#
|
||||
)!
|
||||
After
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -305,7 +277,7 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInlineCode() {
|
||||
let document = Document(#"When `x = 3`, that means `x + 2 = 5`"#)!
|
||||
let document: Document = #"When `x = 3`, that means `x + 2 = 5`"#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -313,7 +285,7 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testInlineCodeStrong() {
|
||||
let document = Document(#"When `x = 3`, that means **`x + 2 = 5`**"#)!
|
||||
let document: Document = #"When `x = 3`, that means **`x + 2 = 5`**"#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -321,13 +293,11 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStrong() {
|
||||
let document = Document(
|
||||
#"""
|
||||
The music video for Rihanna’s song **American Oxygen** depicts various
|
||||
moments from American history, including the inauguration of Barack
|
||||
Obama.
|
||||
"""#
|
||||
)!
|
||||
let document: Document = #"""
|
||||
The music video for Rihanna’s song **American Oxygen** depicts various
|
||||
moments from American history, including the inauguration of Barack
|
||||
Obama.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -335,12 +305,10 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testEmphasis() {
|
||||
let document = Document(
|
||||
#"""
|
||||
Why, sometimes I’ve believed as many as _six_ impossible things before
|
||||
breakfast.
|
||||
"""#
|
||||
)!
|
||||
let document: Document = #"""
|
||||
Why, sometimes I’ve believed as many as _six_ impossible things before
|
||||
breakfast.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -348,11 +316,9 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStrongAndEmphasis() {
|
||||
let document = Document(
|
||||
#"""
|
||||
**Everyone _must_ attend the meeting at 5 o’clock today.**
|
||||
"""#
|
||||
)!
|
||||
let document: Document = #"""
|
||||
**Everyone _must_ attend the meeting at 5 o’clock today.**
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -360,13 +326,11 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testLink() {
|
||||
let document = Document(
|
||||
#"""
|
||||
[Hurricane](https://en.wikipedia.org/wiki/Tropical_cyclone) Erika was the
|
||||
strongest and longest-lasting tropical cyclone in the 1997 Atlantic
|
||||
[hurricane](https://en.wikipedia.org/wiki/Tropical_cyclone) season.
|
||||
"""#
|
||||
)!
|
||||
let document: Document = #"""
|
||||
[Hurricane](https://en.wikipedia.org/wiki/Tropical_cyclone) Erika was the
|
||||
strongest and longest-lasting tropical cyclone in the 1997 Atlantic
|
||||
[hurricane](https://en.wikipedia.org/wiki/Tropical_cyclone) season.
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -374,14 +338,12 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testLinkWithTitle() {
|
||||
let document = Document(
|
||||
#"""
|
||||
[Hurricane][1] Erika was the strongest and longest-lasting tropical cyclone
|
||||
in the 1997 Atlantic [hurricane][1] season.
|
||||
let document: Document = #"""
|
||||
[Hurricane][1] Erika was the strongest and longest-lasting tropical cyclone
|
||||
in the 1997 Atlantic [hurricane][1] season.
|
||||
|
||||
[1]:https://en.wikipedia.org/wiki/Tropical_cyclone "Tropical cyclone"
|
||||
"""#
|
||||
)!
|
||||
[1]:https://en.wikipedia.org/wiki/Tropical_cyclone "Tropical cyclone"
|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
@@ -389,13 +351,11 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testImage() {
|
||||
let document = Document(
|
||||
#"""
|
||||
CommonMark favicon:
|
||||
let document: Document = #"""
|
||||
CommonMark favicon:
|
||||
|
||||

|
||||
"""#
|
||||
)!
|
||||

|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(
|
||||
document: document,
|
||||
@@ -409,13 +369,11 @@ final class NSAttributedStringTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testImageWithoutAttachment() {
|
||||
let document = Document(
|
||||
#"""
|
||||
This image does not have an attachment:
|
||||
let document: Document = #"""
|
||||
This image does not have an attachment:
|
||||
|
||||

|
||||
"""#
|
||||
)!
|
||||

|
||||
"""#
|
||||
|
||||
let attributedString = NSAttributedString(document: document, style: style)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user