diff --git a/.gitignore b/.gitignore
index 4d2a4d6d..99e7978e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,47 @@ pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/dictionaries
+
+# Sensitive or high-churn files:
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.xml
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+
+# Gradle:
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Mongo Explorer plugin:
+.idea/**/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..de2210c9
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1 @@
+language: dart
\ No newline at end of file
diff --git a/README.md b/README.md
index 946fd9ed..2bb57ee8 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,83 @@
-# html_builder
+# html
+[](https://pub.dartlang.org/packages/angel_html)
+[](https://travis-ci.org/angel-dart/html)
+
A plug-in that allows you to return html_builder AST's from request handlers, and have them sent as HTML automatically.
+
+[`package:html_builder`](https://github.com/thosakwe/html_builder) is a simple virtual DOM library
+(without diffing, you can find that
+[here](https://github.com/thosakwe/html_builder_vdom)), with a handy Dart DSL that makes it easy to build HTML
+AST's:
+
+```dart
+import 'package:html_builder/elements.dart';
+
+Node myDom = html(lang: 'en', c: [
+ head(c: [
+ meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
+ title(c: [
+ text('html_builder example page')
+ ]),
+ ]),
+ body(c: [
+ h1(c: [
+ text('Hello world!'),
+ ]),
+ ]),
+]);
+```
+
+This plug-in means that you can now `return` these AST's, and Angel will automatically send them to
+clients. Ultimately, the implication is that you can use `html_builder` as a substitute for a
+templating system within Dart. With [hot reloading](https://github.com/angel-dart/hot), you won't
+even need to reload your server (as it should be).
+
+# Installation
+In your `pubspec.yaml`:
+
+```yaml
+dependencies:
+ angel_html: ^1.0.0
+```
+
+# Usage
+The `renderHtml` function does all the magic for you.
+
+```dart
+configureServer(Angel app) async {
+ // Wire it up!
+ app.before.add(renderHtml());
+
+ // You can pass a custom StringRenderer if you need more control over the output.
+ app.before.add(renderHtml(renderer: new StringRenderer(html5: false)));
+
+ app.get('/greet/:name', (RequestContext req) {
+ return html(lang: 'en', c: [
+ head(c: [
+ meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
+ title(c: [
+ text('Greetings!')
+ ]),
+ ]),
+ body(c: [
+ h1(c: [
+ text('Hello, ${req.params['id']}!'),
+ ]),
+ ]),
+ ]);
+ });
+}
+```
+
+By default, `renderHtml` will ignore the client's `Accept` header. However, if you pass
+`enforceAcceptHeader` as `true`, then a `406 Not Acceptable` error will be thrown if the
+client doesn't accept `*/*` or `text/html`.
+
+```dart
+configureServer(Angel app) async {
+ // Wire it up!
+ app.before.add(renderHtml(enforceAcceptHeader: true));
+
+ // ...
+}
+```
\ No newline at end of file
diff --git a/lib/angel_html.dart b/lib/angel_html.dart
new file mode 100644
index 00000000..99563629
--- /dev/null
+++ b/lib/angel_html.dart
@@ -0,0 +1,35 @@
+import 'dart:async';
+import 'package:angel_framework/angel_framework.dart';
+import 'package:html_builder/html_builder.dart';
+
+/// Returns a [RequestMiddleware] that allows you to return `html_builder` [Node]s as responses.
+///
+/// You can provide a custom [renderer]. The default renders minified HTML5 pages.
+///
+/// Set [enforceAcceptHeader] to `true` to throw a `406 Not Acceptable` if the client doesn't accept HTML responses.
+RequestMiddleware renderHtml(
+ {StringRenderer renderer, bool enforceAcceptHeader}) {
+ renderer ??= new StringRenderer(pretty: false, html5: true);
+
+ return (RequestContext req, ResponseContext res) {
+ var oldSerializer = res.serializer;
+
+ res.serializer = (data) {
+ if (data is! Node)
+ return oldSerializer(data);
+ else {
+ if (enforceAcceptHeader == true && !req.accepts('text/html'))
+ throw new AngelHttpException.notAcceptable();
+
+ var content = renderer.render(data);
+ res
+ ..headers['content-type'] = 'text/html'
+ ..write(content)
+ ..end();
+ return '';
+ }
+ };
+
+ return new Future.value(true);
+ };
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 00000000..7cfb322a
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,14 @@
+name: angel_html
+version: 1.0.0
+description: Easily render html_builder AST's as responses in Angel.
+author: Tobe O
+homepage: https://github.com/angel-dart/html_builder
+environment:
+ sdk: ">=1.19.0"
+dependencies:
+ angel_framework: ^1.0.0
+ html_builder: ^1.0.0
+dev_dependencies:
+ angel_test: ^1.0.0
+ html: ^0.13.2
+ test: ^0.12.0
\ No newline at end of file
diff --git a/test/all_test.dart b/test/all_test.dart
new file mode 100644
index 00000000..92795a83
--- /dev/null
+++ b/test/all_test.dart
@@ -0,0 +1,75 @@
+import 'package:angel_framework/angel_framework.dart';
+import 'package:angel_html/angel_html.dart';
+import 'package:angel_test/angel_test.dart';
+import 'package:html_builder/elements.dart';
+import 'package:html_builder/html_builder.dart';
+import 'package:test/test.dart';
+
+main() {
+ Angel app;
+ TestClient client;
+
+ setUp(() async {
+ app = new Angel();
+
+ app.before.add(renderHtml());
+
+ app.get('/html', () {
+ return html(c: [
+ head(c: [
+ title(c: [text('ok')])
+ ])
+ ]);
+ });
+
+ app
+ .chain(renderHtml(
+ enforceAcceptHeader: true,
+ renderer: new StringRenderer(doctype: null, pretty: false)))
+ .get('/strict', () {
+ return div(c: [text('strict')]);
+ });
+
+ client = await connectTo(app);
+ });
+
+ tearDown(() => client.close());
+
+ test('sets content type and body', () async {
+ var response = await client.get('/html');
+ print('Response: ${response.body}');
+
+ expect(
+ response,
+ allOf(hasContentType('text/html'),
+ hasBody('ok')));
+ });
+
+ group('enforce accept header', () {
+ test('sends if correct accept or wildcard', () async {
+ var response = await client.get('/strict', headers: {'accept': '*/*'});
+ print('Response: ${response.body}');
+ expect(response,
+ allOf(hasContentType('text/html'), hasBody('strict
')));
+
+ response = await client.get('/strict',
+ headers: {'accept': 'text/html,application/json,text/xml'});
+ print('Response: ${response.body}');
+ expect(response,
+ allOf(hasContentType('text/html'), hasBody('strict
')));
+ });
+
+ test('throws if incorrect or no accept', () async {
+ var response = await client.get('/strict');
+ print('Response: ${response.body}');
+ expect(response,
+ isAngelHttpException(statusCode: 406, message: '406 Not Acceptable'));
+
+ response = await client
+ .get('/strict', headers: {'accept': 'application/json,text/xml'});
+ print('Response: ${response.body}');
+ expect(response,
+ isAngelHttpException(statusCode: 406, message: '406 Not Acceptable'));
+ });
+ });
+}