Commit 48c7a04f authored by Viktor Lidholt's avatar Viktor Lidholt

Adds initial version of Markdown renderer

parent b193854b
# Flutter Markdown
A markdown renderer for Flutter. It supports the
[original format](https://daringfireball.net/projects/markdown/), but no inline
html.
## Getting Started
Using the Markdown widget is simple, just pass in the source markdown as a
string:
new Markdown(data: markdownSource);
If you do not want the padding or scrolling behavior, use the MarkdownBody
instead:
new MarkdownBody(data: markdownSource);
By default, Markdown uses the formatting from the current material design theme,
but it's possible to create your own custom styling. Use the MarkdownStyle class
to pass in your own style. If you don't want to use Markdown outside of material
design, use the MarkdownRaw class.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
const String _kMarkdownData = """# Markdown Example
Markdown allows you to easily include formatted text, images, and even formatted Dart code in your app.
## Styling
Style text as _italic_, __bold__, or `inline code`.
- Use bulleted lists
- To better clarify
- Your points
## Code blocks
Formatted Dart code looks really pretty too. This is an example of how to create your own Markdown widget:
new Markdown(data: "Hello _world_!");
Enjoy!
""";
void main() {
runApp(new MaterialApp(
title: "Markdown Demo",
routes: <String, RouteBuilder>{
'/': (RouteArguments args) => new Scaffold(
toolBar: new ToolBar(center: new Text("Markdown Demo")),
body: new Markdown(data: _kMarkdownData)
)
}
));
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library flutter_markdown;
export 'src/markdown.dart';
export 'src/markdown_style.dart';
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
library flutter_markdown;
export 'src/markdown_raw.dart';
export 'src/markdown_style_raw.dart';
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'markdown_raw.dart';
import 'markdown_style.dart';
/// A [Widget] that renders markdown formatted text. It supports all standard
/// markdowns from the original markdown specification found here:
/// https://daringfireball.net/projects/markdown/ The rendered markdown is
/// placed in a padded scrolling view port. If you do not want the scrolling
/// behaviour, use the [MarkdownBody] class instead.
class Markdown extends MarkdownRaw {
/// Creates a new Markdown [Widget] that renders the markdown formatted string
/// passed in as [data]. By default the markdown will be rendered using the
/// styles from the current theme, but you can optionally pass in a custom
/// [markdownStyle] that specifies colors and fonts to use. Code blocks are
/// by default not using syntax highlighting, but it's possible to pass in
/// a custom [syntaxHighlighter].
///
/// new Markdown(data: "Hello _world_!");
Markdown({
String data,
SyntaxHighlighter syntaxHighlighter,
MarkdownStyle markdownStyle
}) : super(
data: data,
syntaxHighlighter: syntaxHighlighter,
markdownStyle: markdownStyle
);
MarkdownBody createMarkdownBody({
String data,
MarkdownStyle markdownStyle,
SyntaxHighlighter syntaxHighlighter
}) {
return new MarkdownBody(
data: data,
markdownStyle: markdownStyle,
syntaxHighlighter: syntaxHighlighter
);
}
}
/// A [Widget] that renders markdown formatted text. It supports all standard
/// markdowns from the original markdown specification found here:
/// https://daringfireball.net/projects/markdown/ This class doesn't implement
/// any scrolling behavior, if you want scrolling either wrap the widget in
/// a [ScrollableViewport] or use the [Markdown] widget.
class MarkdownBody extends MarkdownBodyRaw {
/// Creates a new Markdown [Widget] that renders the markdown formatted string
/// passed in as [data]. By default the markdown will be rendered using the
/// styles from the current theme, but you can optionally pass in a custom
/// [markdownStyle] that specifies colors and fonts to use. Code blocks are
/// by default not using syntax highlighting, but it's possible to pass in
/// a custom [syntaxHighlighter].
///
/// Typically, you may want to wrap the [MarkdownBody] widget in a [Padding] and
/// a [ScrollableViewport], or use the [Markdown] class
///
/// new ScrollableViewport(
/// child: new Padding(
/// padding: new EdgeDims.all(16.0),
/// child: new Markdown(data: markdownSource)
/// )
/// )
MarkdownBody({
String data,
SyntaxHighlighter syntaxHighlighter,
MarkdownStyle markdownStyle
}) : super(
data: data,
syntaxHighlighter: syntaxHighlighter,
markdownStyle: markdownStyle
);
MarkdownStyle createDefaultStyle(BuildContext context) {
return new MarkdownStyle.defaultFromTheme(Theme.of(context));
}
}
This diff is collapsed.
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'markdown_style_raw.dart';
/// Style used for rendering markdown formatted text using the [MarkdownBody]
/// widget.
class MarkdownStyle extends MarkdownStyleRaw{
/// Creates a [MarkdownStyle] from the [TextStyle]s in the provided [theme].
MarkdownStyle.defaultFromTheme(ThemeData theme) : super(
a: new TextStyle(color: Colors.blue[500]),
p: theme.text.body1,
code: new TextStyle(
color: Colors.grey[700],
fontFamily: "monospace",
fontSize: theme.text.body1.fontSize * 0.85
),
h1: theme.text.headline,
h2: theme.text.title,
h3: theme.text.subhead,
h4: theme.text.body2,
h5: theme.text.body2,
h6: theme.text.body2,
em: new TextStyle(fontStyle: FontStyle.italic),
strong: new TextStyle(fontWeight: FontWeight.bold),
blockquote: theme.text.body1,
blockSpacing: 8.0,
listIndent: 32.0,
blockquotePadding: 8.0,
blockquoteDecoration: new BoxDecoration(
backgroundColor: Colors.blue[100],
borderRadius: 2.0
),
codeblockPadding: 8.0,
codeblockDecoration: new BoxDecoration(
backgroundColor: Colors.grey[100],
borderRadius: 2.0
)
);
/// Creates a [MarkdownStyle] from the [TextStyle]s in the provided [theme].
/// This style uses larger fonts for the headings than in
/// [MarkdownStyle.defaultFromTheme].
MarkdownStyle.largeFromTheme(ThemeData theme) : super (
a: new TextStyle(color: Colors.blue[500]),
p: theme.text.body1,
code: new TextStyle(
color: Colors.grey[700],
fontFamily: "monospace",
fontSize: theme.text.body1.fontSize * 0.85
),
h1: theme.text.display3,
h2: theme.text.display2,
h3: theme.text.display1,
h4: theme.text.headline,
h5: theme.text.title,
h6: theme.text.subhead,
em: new TextStyle(fontStyle: FontStyle.italic),
strong: new TextStyle(fontWeight: FontWeight.bold),
blockquote: theme.text.body1,
blockSpacing: 8.0,
listIndent: 32.0,
blockquotePadding: 8.0,
blockquoteDecoration: new BoxDecoration(
backgroundColor: Colors.blue[100],
borderRadius: 2.0
),
codeblockPadding: 8.0,
codeblockDecoration: new BoxDecoration(
backgroundColor: Colors.grey[100],
borderRadius: 2.0
)
);
}
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
/// Style used for rendering markdown formatted text using the [MarkdownBody]
/// widget.
class MarkdownStyleRaw {
/// Creates a new [MarkdownStyleRaw]
MarkdownStyleRaw({
this.a,
this.p,
this.code,
this.h1,
this.h2,
this.h3,
this.h4,
this.h5,
this.h6,
this.em,
this.strong,
this.blockquote,
this.blockSpacing,
this.listIndent,
this.blockquotePadding,
this.blockquoteDecoration,
this.codeblockPadding,
this.codeblockDecoration
}) {
_init();
}
/// Creates a new [MarkdownStyleRaw] based on the current style, with the
/// provided paramaters overridden.
MarkdownStyleRaw copyWith({
TextStyle a,
TextStyle p,
TextStyle code,
TextStyle h1,
TextStyle h2,
TextStyle h3,
TextStyle h4,
TextStyle h5,
TextStyle h6,
TextStyle em,
TextStyle strong,
TextStyle blockquote,
double blockSpacing,
double listIndent,
double blockquotePadding,
BoxDecoration blockquoteDecoration,
double codeblockPadding,
BoxDecoration codeblockDecoration
}) {
return new MarkdownStyleRaw(
a: a != null ? a : this.a,
p: p != null ? p : this.p,
code: code != null ? code : this.code,
h1: h1 != null ? h1 : this.h1,
h2: h2 != null ? h2 : this.h2,
h3: h3 != null ? h3 : this.h3,
h4: h4 != null ? h4 : this.h4,
h5: h5 != null ? h5 : this.h5,
h6: h6 != null ? h6 : this.h6,
em: em != null ? em : this.em,
strong: strong != null ? strong : this.strong,
blockquote: blockquote != null ? blockquote : this.blockquote,
blockSpacing: blockSpacing != null ? blockSpacing : this.blockSpacing,
listIndent: listIndent != null ? listIndent : this.listIndent,
blockquotePadding: blockquotePadding != null ? blockquotePadding : this.blockquotePadding,
blockquoteDecoration: blockquoteDecoration != null ? blockquoteDecoration : this.blockquoteDecoration,
codeblockPadding: codeblockPadding != null ? codeblockPadding : this.codeblockPadding,
codeblockDecoration: codeblockDecoration != null ? codeblockDecoration : this.codeblockDecoration
);
}
final TextStyle a;
final TextStyle p;
final TextStyle code;
final TextStyle h1;
final TextStyle h2;
final TextStyle h3;
final TextStyle h4;
final TextStyle h5;
final TextStyle h6;
final TextStyle em;
final TextStyle strong;
final TextStyle blockquote;
final double blockSpacing;
final double listIndent;
final double blockquotePadding;
final BoxDecoration blockquoteDecoration;
final double codeblockPadding;
final BoxDecoration codeblockDecoration;
Map<String, TextStyle> _styles;
Map<String, TextStyle> get styles => _styles;
void _init() {
_styles = {
'a': a,
'p': p,
'li': p,
'code': code,
'pre': p,
'h1': h1,
'h2': h2,
'h3': h3,
'h4': h4,
'h5': h5,
'h6': h6,
'em': em,
'strong': strong,
'blockquote': blockquote
};
}
}
name: flutter_markdown
description: A markdown renderer for Flutter.
version: 0.1.0
author: Flutter Authors <flutter-dev@googlegroups.com>
homepage: http://flutter.io
dependencies:
flutter:
path: ../flutter
markdown: "0.9.0"
string_scanner: "0.1.4+1"
dev_dependencies:
flutter_tools:
path: ../flutter_tools
test: any # constrained by the dependency in flutter_tools
flutter_test:
path: ../flutter_test
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:test/test.dart';
import 'package:flutter/material.dart';
void main() {
test("Simple string", () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new MarkdownBody(data: "Hello"));
Element textElement = tester.findElement((Element element) => element.widget is RichText);
RichText textWidget = textElement.widget;
TextSpan textSpan = textWidget.text;
List<Element> elements = _listElements(tester);
_expectWidgetTypes(elements, <Type>[MarkdownBody, Column, Container, Padding, RichText]);
expect(textSpan.children[0].text, equals("Hello"));
});
});
test("Header", () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new MarkdownBody(data: "# Header"));
Element textElement = tester.findElement((Element element) => element.widget is RichText);
RichText textWidget = textElement.widget;
TextSpan textSpan = textWidget.text;
List<Element> elements = _listElements(tester);
_expectWidgetTypes(elements, <Type>[MarkdownBody, Column, Container, Padding, RichText]);
expect(textSpan.children[0].text, equals("Header"));
});
});
test("Empty string", () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new MarkdownBody(data: ""));
List<Element> elements = _listElements(tester);
_expectWidgetTypes(elements, <Type>[MarkdownBody, Column]);
});
});
test("Ordered list", () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new MarkdownBody(data: "1. Item 1\n1. Item 2\n2. Item 3"));
List<Element> elements = _listElements(tester);
_expectTextStrings(elements, <String>[
"1.",
"Item 1",
"2.",
"Item 2",
"3.",
"Item 3"]
);
});
});
test("Unordered list", () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new MarkdownBody(data: "- Item 1\n- Item 2\n- Item 3"));
List<Element> elements = _listElements(tester);
_expectTextStrings(elements, <String>[
"•",
"Item 1",
"•",
"Item 2",
"•",
"Item 3"]
);
});
});
test("Scrollable wrapping", () {
testWidgets((WidgetTester tester) {
tester.pumpWidget(new Markdown(data: ""));
List<Element> elements = _listElements(tester);
for (Element element in elements) print("e: $element");
_expectWidgetTypes(elements, <Type>[
Markdown,
ScrollableViewport,
null, null, null, null, null, // ScrollableViewport internals
Padding,
MarkdownBody,
Column
]);
});
});
}
List<Element> _listElements(WidgetTester tester) {
List<Element> elements = <Element>[];
tester.walkElements((Element element) {
elements.add(element);
});
return elements;
}
void _expectWidgetTypes(List<Element> elements, List<Type> types) {
expect(elements.length, equals(types.length));
for (int i = 0; i < elements.length; i += 1) {
Element element = elements[i];
Type type = types[i];
if (type == null) continue;
expect(element.widget.runtimeType, equals(type));
}
}
void _expectTextStrings(List<Element> elements, List<String> strings) {
int currentString = 0;
for (Element element in elements) {
Widget widget = element.widget;
if (widget is RichText) {
TextSpan span = widget.text;
String text = _extractTextFromTextSpan(span);
expect(text, equals(strings[currentString]));
currentString += 1;
}
}
}
String _extractTextFromTextSpan(TextSpan span) {
String text = span.text ?? "";
if (span.children != null) {
for (TextSpan child in span.children) {
text += _extractTextFromTextSpan(child);
}
}
return text;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment