Skip to content

Commit

Permalink
Merge pull request #5 from iwasrobbed/downview
Browse files Browse the repository at this point in the history
DownView rendering to close #3
  • Loading branch information
rob phillips committed Jun 2, 2016
2 parents a8186a3 + b79f8a0 commit a410f58
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 8 deletions.
3 changes: 2 additions & 1 deletion Down.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |spec|
spec.name = "Down"
spec.summary = "Blazing fast Markdown rendering in Swift, built upon cmark."
spec.version = "0.1.1"
spec.version = "0.2"
spec.homepage = "https://github.com/iwasrobbed/Down"
spec.license = { :type => "MIT", :file => "LICENSE" }
spec.authors = { "Rob Phillips" => "rob@desideratalabs.co" }
Expand All @@ -13,4 +13,5 @@ Pod::Spec.new do |spec|
spec.module_name = "Down"
spec.preserve_path = 'Source/cmark/module.modulemap'
spec.pod_target_xcconfig = { 'SWIFT_INCLUDE_PATHS' => '$(SRCROOT)/Down/Source/cmark/**' }
spec.resource = 'Resources/DownView.bundle'
end
32 changes: 32 additions & 0 deletions Down.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
D41689B31CFFE28200E5802B /* DownViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41689B21CFFE28200E5802B /* DownViewTests.swift */; };
D41689B61CFFE6BB00E5802B /* DownView.bundle in Resources */ = {isa = PBXBuildFile; fileRef = D41689B51CFFE6BB00E5802B /* DownView.bundle */; };
D4201E8B1CFA5151008EEC6E /* Down.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4201E801CFA5151008EEC6E /* Down.framework */; };
D4201EEF1CFA59AD008EEC6E /* BindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4201EC51CFA59A5008EEC6E /* BindingTests.swift */; };
D4201EF11CFA59F2008EEC6E /* Down.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4201EF01CFA59F2008EEC6E /* Down.swift */; };
Expand Down Expand Up @@ -48,6 +50,8 @@
D4201F441CFA5D63008EEC6E /* utf8.h in Headers */ = {isa = PBXBuildFile; fileRef = D4201F1D1CFA5D63008EEC6E /* utf8.h */; };
D4201F451CFA5D63008EEC6E /* xml.c in Sources */ = {isa = PBXBuildFile; fileRef = D4201F1E1CFA5D63008EEC6E /* xml.c */; settings = {COMPILER_FLAGS = "-w"; }; };
D43AE5CA1CFFAE4D006E1522 /* NSAttributedString+HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43AE5C91CFFAE4D006E1522 /* NSAttributedString+HTML.swift */; };
D43AE5DA1CFFD0D0006E1522 /* DownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43AE5D91CFFD0D0006E1522 /* DownView.swift */; };
D43AE5DC1CFFD473006E1522 /* String+ToHTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43AE5DB1CFFD473006E1522 /* String+ToHTML.swift */; };
D44875E41CFA6B200037A624 /* DownRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44875E31CFA6B200037A624 /* DownRenderable.swift */; };
D44875E61CFA6B660037A624 /* DownHTMLRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44875E51CFA6B660037A624 /* DownHTMLRenderable.swift */; };
D44875EA1CFA6CF30037A624 /* DownErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44875E81CFA6CF30037A624 /* DownErrors.swift */; };
Expand All @@ -71,6 +75,8 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
D41689B21CFFE28200E5802B /* DownViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownViewTests.swift; sourceTree = "<group>"; };
D41689B51CFFE6BB00E5802B /* DownView.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = DownView.bundle; sourceTree = "<group>"; };
D4201E801CFA5151008EEC6E /* Down.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Down.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D4201E8A1CFA5151008EEC6E /* DownTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DownTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D4201E9A1CFA59A5008EEC6E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -115,6 +121,8 @@
D4201F1E1CFA5D63008EEC6E /* xml.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xml.c; sourceTree = "<group>"; };
D42869501CFF501200FACB4C /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
D43AE5C91CFFAE4D006E1522 /* NSAttributedString+HTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+HTML.swift"; sourceTree = "<group>"; };
D43AE5D91CFFD0D0006E1522 /* DownView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownView.swift; sourceTree = "<group>"; };
D43AE5DB1CFFD473006E1522 /* String+ToHTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+ToHTML.swift"; sourceTree = "<group>"; };
D44875E31CFA6B200037A624 /* DownRenderable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownRenderable.swift; sourceTree = "<group>"; };
D44875E51CFA6B660037A624 /* DownHTMLRenderable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownHTMLRenderable.swift; sourceTree = "<group>"; };
D44875E81CFA6CF30037A624 /* DownErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownErrors.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -147,11 +155,20 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
D41689B41CFFE6BB00E5802B /* Resources */ = {
isa = PBXGroup;
children = (
D41689B51CFFE6BB00E5802B /* DownView.bundle */,
);
path = Resources;
sourceTree = "<group>";
};
D4201E761CFA5151008EEC6E = {
isa = PBXGroup;
children = (
D4201E9A1CFA59A5008EEC6E /* Info.plist */,
D4201E9B1CFA59A5008EEC6E /* Source */,
D41689B41CFFE6BB00E5802B /* Resources */,
D4201EC41CFA59A5008EEC6E /* Tests */,
D4201E811CFA5151008EEC6E /* Products */,
);
Expand All @@ -174,6 +191,7 @@
D44875E71CFA6CF30037A624 /* Enums & Options */,
D43AE5C81CFFAE39006E1522 /* Extensions */,
D44875E21CFA6B120037A624 /* Renderers */,
D43AE5CB1CFFD068006E1522 /* Views */,
D4201EF61CFA5D63008EEC6E /* cmark */,
);
path = Source;
Expand All @@ -183,6 +201,7 @@
isa = PBXGroup;
children = (
D4201EC51CFA59A5008EEC6E /* BindingTests.swift */,
D41689B21CFFE28200E5802B /* DownViewTests.swift */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -236,10 +255,19 @@
isa = PBXGroup;
children = (
D43AE5C91CFFAE4D006E1522 /* NSAttributedString+HTML.swift */,
D43AE5DB1CFFD473006E1522 /* String+ToHTML.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
D43AE5CB1CFFD068006E1522 /* Views */ = {
isa = PBXGroup;
children = (
D43AE5D91CFFD0D0006E1522 /* DownView.swift */,
);
path = Views;
sourceTree = "<group>";
};
D44875E21CFA6B120037A624 /* Renderers */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -372,6 +400,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D41689B61CFFE6BB00E5802B /* DownView.bundle in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -395,6 +424,7 @@
D4201F391CFA5D63008EEC6E /* man.c in Sources */,
D4CF88981CFFAC2C00F07FD1 /* DownAttributedStringRenderable.swift in Sources */,
D4201F3D1CFA5D63008EEC6E /* references.c in Sources */,
D43AE5DC1CFFD473006E1522 /* String+ToHTML.swift in Sources */,
D4201F301CFA5D63008EEC6E /* houdini_html_e.c in Sources */,
D4201F3F1CFA5D63008EEC6E /* render.c in Sources */,
D486E9981CFDE2730059FD7C /* DownLaTeXRenderable.swift in Sources */,
Expand All @@ -409,6 +439,7 @@
D4201F201CFA5D63008EEC6E /* buffer.c in Sources */,
D4201F431CFA5D63008EEC6E /* utf8.c in Sources */,
D4DC91141CFDED4B0091CE09 /* DownCommonMarkRenderable.swift in Sources */,
D43AE5DA1CFFD0D0006E1522 /* DownView.swift in Sources */,
D4201F411CFA5D63008EEC6E /* scanners.c in Sources */,
D4201F381CFA5D63008EEC6E /* latex.c in Sources */,
D4201F321CFA5D63008EEC6E /* html.c in Sources */,
Expand All @@ -427,6 +458,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D41689B31CFFE28200E5802B /* DownViewTests.swift in Sources */,
D4201EEF1CFA59AD008EEC6E /* BindingTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Binary file added Images/ohhai.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pod 'Down'
> The library has been extensively fuzz-tested using [american fuzzy lop](http://lcamtuf.coredump.cx/afl). The test suite includes pathological cases that bring many other Markdown parsers to a crawl (for example, thousands-deep nested bracketed text or block quotes).
### Output Formats
* Web View (see DownView class)
* HTML
* XML
* LaTeX
Expand All @@ -32,9 +33,22 @@ pod 'Down'
* NSAttributedString
* AST (abstract syntax tree)

### API
### View Rendering

The `Down` struct has everything you need if you just want out-of-the-box setup.
The `DownView` class offers a very simple way to parse a UTF-8 encoded string with Markdown and convert it to a web view that can be added to any view:

```swift
let downView = try? DownView(frame: self.view.bounds, markdownString: "**Oh Hai**")
// Now add to view or constrain w/ Autolayout
```

Meta example of rendering this README:

![Example gif](Images/ohhai.gif)

### Parsing API

The `Down` struct has everything you need if you just want out-of-the-box setup for parsing and conversion.

```swift
let down = Down(markdownString: "## [Down](https://github.com/iwasrobbed/Down)")
Expand Down
8 changes: 8 additions & 0 deletions Resources/DownView.bundle/css/down.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Resources/DownView.bundle/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=640"/>
<link charset="utf-8" href="css/down.min.css" rel="stylesheet">
<script charset="utf-8" src="js/highlight.min.js" type="text/javascript"></script>
<script charset="utf-8" src="js/down.js" type="text/javascript"></script>
<title></title>
</head>
<body>
DOWN_HTML
</body>
</html>
1 change: 1 addition & 0 deletions Resources/DownView.bundle/js/down.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hljs.initHighlightingOnLoad();
2 changes: 2 additions & 0 deletions Resources/DownView.bundle/js/highlight.min.js

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions Source/Extensions/String+ToHTML.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// String+ToHTML.swift
// Down
//
// Created by Rob Phillips on 6/1/16.
// Copyright © 2016 Glazed Donut, LLC. All rights reserved.
//

import Foundation
import libcmark

extension String {

/**
Generates an HTML string from the contents of the string (self), which should contain CommonMark Markdown
- parameter options: `DownOptions` to modify parsing or rendering, defaulting to `.Default`
- throws: `DownErrors` depending on the scenario
- returns: HTML string
*/
public func toHTML(options: DownOptions = .Default) throws -> String {
let ast = try DownASTRenderer.stringToAST(self, options: options)
let html = try DownHTMLRenderer.astToHTML(ast, options: options)
cmark_node_free(ast)
return html
}

}
5 changes: 1 addition & 4 deletions Source/Renderers/DownHTMLRenderable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ public extension DownHTMLRenderable {
*/
@warn_unused_result
public func toHTML(options: DownOptions = .Default) throws -> String {
let ast = try DownASTRenderer.stringToAST(markdownString, options: options)
let html = try DownHTMLRenderer.astToHTML(ast, options: options)
cmark_node_free(ast)
return html
return try markdownString.toHTML(options)
}
}

Expand Down
78 changes: 78 additions & 0 deletions Source/Views/DownView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// DownView.swift
// Down
//
// Created by Rob Phillips on 6/1/16.
// Copyright © 2016 Glazed Donut, LLC. All rights reserved.
//

import WebKit

// MARK: - Public API

public class DownView: WKWebView {

/**
Initializes a web view with the results of rendering a CommonMark Markdown string
- parameter frame: The frame size of the web view
- parameter markdownString: A string containing CommonMark Markdown
- parameter openLinksInBrowser: Whether or not to open links using an external browser
- returns: An instance of Self
*/
@warn_unused_result
public init(frame: CGRect, markdownString: String, openLinksInBrowser: Bool = true) throws {
super.init(frame: frame, configuration: WKWebViewConfiguration())

if openLinksInBrowser { navigationDelegate = self }
try loadHTMLView(markdownString)
}

// MARK: - Private Properties

private let bundle: NSBundle = {
let bundle = NSBundle(forClass: DownView.self)
let url = bundle.URLForResource("DownView", withExtension: "bundle")!
return NSBundle(URL: url)!
}()

private lazy var baseURL: NSURL = {
return self.bundle.URLForResource("index", withExtension: "html")!
}()
}

// MARK: - Private API

private extension DownView {

func loadHTMLView(markdownString: String) throws {
let htmlString = try markdownString.toHTML()
let pageHTMLString = try htmlFromTemplate(htmlString)
loadHTMLString(pageHTMLString, baseURL: baseURL)
}

func htmlFromTemplate(htmlString: String) throws -> String {
let template = try NSString(contentsOfURL: baseURL, encoding: NSUTF8StringEncoding)
return template.stringByReplacingOccurrencesOfString("DOWN_HTML", withString: htmlString)
}

}

// MARK: - WKNavigationDelegate

extension DownView: WKNavigationDelegate {

public func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.URL else { return }

switch navigationAction.navigationType {
case .LinkActivated:
decisionHandler(.Cancel)
UIApplication.sharedApplication().openURL(url)
default:
decisionHandler(.Allow)
}
}

}
Loading

0 comments on commit a410f58

Please sign in to comment.