引入三方flutter_markdown

gi
This commit is contained in:
sanfan.hx
2019-06-11 19:26:39 +08:00
parent f1a6b25014
commit 998aae24f2
9 changed files with 1006 additions and 38 deletions

View File

@ -0,0 +1,27 @@
// Copyright 2017 Google, Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,39 @@
# Flutter Markdown
[![pub package](https://img.shields.io/pub/v/flutter_markdown.svg)](https://pub.dartlang.org/packages/flutter_markdown)
[![Build Status](https://travis-ci.org/flutter/flutter_markdown.svg?branch=master)](https://travis-ci.org/flutter/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.
## Image support
The `Img` tag only supports the following image locations:
* From the network: Use a URL prefixed by either `http://` or `https://`.
* From local files on the device: Use an absolute path to the file, for example by
concatenating the file name with the path returned by a known storage location,
such as those provided by the [`path_provider`](https://pub.dartlang.org/packages/path_provider)
plugin.
* From image locations referring to bundled assets: Use an asset name prefixed by `resource:`.
like `resource:assets/image.png`.

View File

@ -0,0 +1,10 @@
// 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.
/// A library to render markdown formatted text.
library flutter_markdown;
export 'src/builder.dart';
export 'src/style_sheet.dart';
export 'src/widget.dart';

View File

@ -0,0 +1,376 @@
// 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 'dart:io';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:path/path.dart' as p;
import 'style_sheet.dart';
typedef Widget DemoBuilder(Map<String, dynamic> attrs);
final Set<String> _kBlockTags = new Set<String>.from(<String>[
'p',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'li',
'blockquote',
'pre',
'ol',
'ul',
'hr',
]);
const List<String> _kListTags = const <String>['ul', 'ol'];
bool _isBlockTag(String tag) => _kBlockTags.contains(tag);
bool _isListTag(String tag) => _kListTags.contains(tag);
class _BlockElement {
_BlockElement(this.tag);
final String tag;
final List<Widget> children = <Widget>[];
int nextListIndex = 0;
}
/// A collection of widgets that should be placed adjacent to (inline with)
/// other inline elements in the same parent block.
///
/// Inline elements can be textual (a/em/strong) represented by [RichText]
/// widgets or images (img) represented by [Image.network] widgets.
///
/// Inline elements can be nested within other inline elements, inheriting their
/// parent's style along with the style of the block they are in.
///
/// When laying out inline widgets, first, any adjacent RichText widgets are
/// merged, then, all inline widgets are enclosed in a parent [Wrap] widget.
class _InlineElement {
_InlineElement(this.tag, {this.style});
final String tag;
/// Created by merging the style defined for this element's [tag] in the
/// delegate's [MarkdownStyleSheet] with the style of its parent.
final TextStyle style;
final List<Widget> children = <Widget>[];
}
/// A delegate used by [MarkdownBuilder] to control the widgets it creates.
abstract class MarkdownBuilderDelegate {
/// Returns a gesture recognizer to use for an `a` element with the given
/// `href` attribute.
GestureRecognizer createLink(String href);
/// Returns formatted text to use to display the given contents of a `pre`
/// element.
///
/// The `styleSheet` is the value of [MarkdownBuilder.styleSheet].
TextSpan formatText(MarkdownStyleSheet styleSheet, String code);
}
/// Builds a [Widget] tree from parsed Markdown.
///
/// See also:
///
/// * [Markdown], which is a widget that parses and displays Markdown.
class MarkdownBuilder implements md.NodeVisitor {
/// Creates an object that builds a [Widget] tree from parsed Markdown.
MarkdownBuilder({
this.delegate,
this.styleSheet,
this.imageDirectory,
this.demoParser
});
/// A delegate that controls how link and `pre` elements behave.
final MarkdownBuilderDelegate delegate;
/// Defines which [TextStyle] objects to use for each type of element.
final MarkdownStyleSheet styleSheet;
final DemoBuilder demoParser;
/// The base directory holding images referenced by Img tags with local file paths.
final Directory imageDirectory;
final List<String> _listIndents = <String>[];
final List<_BlockElement> _blocks = <_BlockElement>[];
final List<_InlineElement> _inlines = <_InlineElement>[];
final List<GestureRecognizer> _linkHandlers = <GestureRecognizer>[];
/// Returns widgets that display the given Markdown nodes.
///
/// The returned widgets are typically used as children in a [ListView].
List<Widget> build(List<md.Node> nodes) {
_listIndents.clear();
_blocks.clear();
_inlines.clear();
_linkHandlers.clear();
_blocks.add(new _BlockElement(null));
for (md.Node node in nodes) {
assert(_blocks.length == 1);
node.accept(this);
}
assert(_inlines.isEmpty);
return _blocks.single.children;
}
@override
void visitText(md.Text text) {
if (_blocks.last.tag == null) // Don't allow text directly under the root.
return;
_addParentInlineIfNeeded(_blocks.last.tag);
final TextSpan span = _blocks.last.tag == 'pre'
? delegate.formatText(styleSheet, text.text)
: new TextSpan(
style: _inlines.last.style,
text: text.text,
recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null,
);
_inlines.last.children.add(new RichText(
textScaleFactor: styleSheet.textScaleFactor,
text: span,
));
}
@override
bool visitElementBefore(md.Element element) {
// print("visitElementBefore ${element.tag}");
final String tag = element.tag;
if (_isBlockTag(tag)) {
_addAnonymousBlockIfNeeded(styleSheet.styles[tag]);
if (_isListTag(tag))
_listIndents.add(tag);
_blocks.add(new _BlockElement(tag));
} else {
_addParentInlineIfNeeded(_blocks.last.tag);
TextStyle parentStyle = _inlines.last.style;
_inlines.add(new _InlineElement(
tag,
style: parentStyle.merge(styleSheet.styles[tag]),
));
}
if (tag == 'a') {
_linkHandlers.add(delegate.createLink(element.attributes['href']));
}
return true;
}
@override
void visitElementAfter(md.Element element) {
final String tag = element.tag;
if (_isBlockTag(tag)) {
_addAnonymousBlockIfNeeded(styleSheet.styles[tag]);
final _BlockElement current = _blocks.removeLast();
Widget child;
if (current.children.isNotEmpty) {
child = new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: current.children,
);
} else {
child = const SizedBox();
}
if (_isListTag(tag)) {
assert(_listIndents.isNotEmpty);
_listIndents.removeLast();
} else if (tag == 'li') {
if (_listIndents.isNotEmpty) {
child = new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new SizedBox(
width: styleSheet.listIndent,
child: _buildBullet(_listIndents.last),
),
new Expanded(child: child)
],
);
}
} else if (tag == 'blockquote') {
child = new DecoratedBox(
decoration: styleSheet.blockquoteDecoration,
child: new Padding(
padding: new EdgeInsets.all(styleSheet.blockquotePadding),
child: child,
),
);
} else if (tag == 'pre') {
child = new DecoratedBox(
decoration: styleSheet.codeblockDecoration,
child: new Padding(
padding: new EdgeInsets.all(styleSheet.codeblockPadding),
child: child,
),
);
} else if (tag == 'hr') {
child = new DecoratedBox(
decoration: styleSheet.horizontalRuleDecoration,
child: child,
);
}
_addBlockChild(child);
} else {
final _InlineElement current = _inlines.removeLast();
final _InlineElement parent = _inlines.last;
if (tag == 'img') {
// create an image widget for this image
current.children.add(_buildImage(element.attributes['src']));
} else if (tag == 'a') {
_linkHandlers.removeLast();
} else if (tag == 'demo') {
current.children.add(_buildGoDemos(element.attributes));
}
if (current.children.isNotEmpty) {
parent.children.addAll(current.children);
}
}
}
Widget _buildGoDemos(Map<String, dynamic> attrs) {
Widget targetGoDemos;
if (demoParser != null) {
targetGoDemos = demoParser(attrs);
}
return targetGoDemos ?? new Text('demo not exits');
}
Widget _buildImage(String src) {
final List<String> parts = src.split('#');
if (parts.isEmpty)
return const SizedBox();
final String path = parts.first;
double width;
double height;
if (parts.length == 2) {
final List<String> dimensions = parts.last.split('x');
if (dimensions.length == 2) {
width = double.parse(dimensions[0]);
height = double.parse(dimensions[1]);
}
}
Uri uri = Uri.parse(path);
Widget child;
if (uri.scheme == 'http' || uri.scheme == 'https') {
child = new Image.network(uri.toString(), width: width, height: height);
} else if (uri.scheme == 'data') {
child = _handleDataSchemeUri(uri, width, height);
} else if (uri.scheme == "resource") {
child = new Image.asset(path.substring(9), width: width, height: height);
} else {
String filePath = (imageDirectory == null
? uri.toFilePath()
: p.join(imageDirectory.path, uri.toFilePath()));
child = new Image.file(new File(filePath), width: width, height: height);
}
if (_linkHandlers.isNotEmpty) {
TapGestureRecognizer recognizer = _linkHandlers.last;
return new GestureDetector(child: child, onTap: recognizer.onTap);
} else {
return child;
}
}
Widget _handleDataSchemeUri(Uri uri, final double width, final double height) {
final String mimeType = uri.data.mimeType;
if (mimeType.startsWith('image/')) {
return new Image.memory(uri.data.contentAsBytes(), width: width, height: height);
} else if (mimeType.startsWith('text/')) {
return new Text(uri.data.contentAsString());
}
return const SizedBox();
}
Widget _buildBullet(String listTag) {
if (listTag == 'ul')
return new Text('', textAlign: TextAlign.center, style: styleSheet.styles['li']);
final int index = _blocks.last.nextListIndex;
return new Padding(
padding: const EdgeInsets.only(right: 5.0),
child: new Text('${index + 1}.', textAlign: TextAlign.right, style: styleSheet.styles['li']),
);
}
void _addParentInlineIfNeeded(String tag) {
if (_inlines.isEmpty) {
_inlines.add(new _InlineElement(
tag,
style: styleSheet.styles[tag],
));
}
}
void _addBlockChild(Widget child) {
final _BlockElement parent = _blocks.last;
if (parent.children.isNotEmpty)
parent.children.add(new SizedBox(height: styleSheet.blockSpacing));
parent.children.add(child);
parent.nextListIndex += 1;
}
void _addAnonymousBlockIfNeeded(TextStyle style) {
if (_inlines.isEmpty) {
return;
}
final _InlineElement inline = _inlines.single;
if (inline.children.isNotEmpty) {
List<Widget> mergedInlines = _mergeInlineChildren(inline);
final Wrap wrap = new Wrap(children: mergedInlines);
_addBlockChild(wrap);
_inlines.clear();
}
}
/// Merges adjacent [TextSpan] children of the given [_InlineElement]
List<Widget> _mergeInlineChildren(_InlineElement inline) {
List<Widget> mergedTexts = <Widget>[];
for (Widget child in inline.children) {
if (mergedTexts.isNotEmpty && mergedTexts.last is RichText && child is RichText) {
RichText previous = mergedTexts.removeLast();
List<TextSpan> children = previous.text.children != null
? new List.from(previous.text.children)
: [previous.text];
children.add(child.text);
TextSpan mergedSpan = new TextSpan(children: children);
mergedTexts.add(new RichText(
textScaleFactor: styleSheet.textScaleFactor,
text: mergedSpan,
));
} else {
mergedTexts.add(child);
}
}
return mergedTexts;
}
}

View File

@ -0,0 +1,307 @@
// 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';
/// Defines which [TextStyle] objects to use for which Markdown elements.
class MarkdownStyleSheet {
/// Creates an explicit mapping of [TextStyle] objects to Markdown elements.
MarkdownStyleSheet({
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.img,
this.blockSpacing,
this.listIndent,
this.blockquotePadding,
this.blockquoteDecoration,
this.codeblockPadding,
this.codeblockDecoration,
this.horizontalRuleDecoration,
this.textScaleFactor = 1.0
}) : _styles = <String, TextStyle>{
'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,
'img': img,
};
/// Creates a [MarkdownStyleSheet] from the [TextStyle]s in the provided [ThemeData].
factory MarkdownStyleSheet.fromTheme(ThemeData theme) {
assert(theme?.textTheme?.body1?.fontSize != null);
return new MarkdownStyleSheet(
a: const TextStyle(color: Colors.blue),
p: theme.textTheme.body1,
code: new TextStyle(
color: Colors.grey.shade700,
fontFamily: "monospace",
fontSize: theme.textTheme.body1.fontSize * 0.85
),
h1: theme.textTheme.headline,
h2: theme.textTheme.title,
h3: theme.textTheme.subhead,
h4: theme.textTheme.body2,
h5: theme.textTheme.body2,
h6: theme.textTheme.body2,
em: const TextStyle(fontStyle: FontStyle.italic),
strong: const TextStyle(fontWeight: FontWeight.bold),
blockquote: theme.textTheme.body1,
img: theme.textTheme.body1,
blockSpacing: 8.0,
listIndent: 32.0,
blockquotePadding: 8.0,
blockquoteDecoration: new BoxDecoration(
color: Colors.blue.shade100,
borderRadius: new BorderRadius.circular(2.0)
),
codeblockPadding: 8.0,
codeblockDecoration: new BoxDecoration(
color: Colors.grey.shade100,
borderRadius: new BorderRadius.circular(2.0)
),
horizontalRuleDecoration: new BoxDecoration(
border: new Border(
top: new BorderSide(width: 5.0, color: Colors.grey.shade300)
),
),
);
}
/// Creates a [MarkdownStyle] from the [TextStyle]s in the provided [ThemeData].
///
/// This constructor uses larger fonts for the headings than in
/// [MarkdownStyle.fromTheme].
factory MarkdownStyleSheet.largeFromTheme(ThemeData theme) {
return new MarkdownStyleSheet(
a: const TextStyle(color: Colors.blue),
p: theme.textTheme.body1,
code: new TextStyle(
color: Colors.grey.shade700,
fontFamily: "monospace",
fontSize: theme.textTheme.body1.fontSize * 0.85
),
h1: theme.textTheme.display3,
h2: theme.textTheme.display2,
h3: theme.textTheme.display1,
h4: theme.textTheme.headline,
h5: theme.textTheme.title,
h6: theme.textTheme.subhead,
em: const TextStyle(fontStyle: FontStyle.italic),
strong: const TextStyle(fontWeight: FontWeight.bold),
blockquote: theme.textTheme.body1,
img: theme.textTheme.body1,
blockSpacing: 8.0,
listIndent: 32.0,
blockquotePadding: 8.0,
blockquoteDecoration: new BoxDecoration(
color: Colors.blue.shade100,
borderRadius: new BorderRadius.circular(2.0)
),
codeblockPadding: 8.0,
codeblockDecoration: new BoxDecoration(
color: Colors.grey.shade100,
borderRadius: new BorderRadius.circular(2.0)
),
horizontalRuleDecoration: new BoxDecoration(
border: new Border(
top: new BorderSide(width: 5.0, color: Colors.grey.shade300)
),
),
);
}
/// Creates a new [MarkdownStyleSheet] based on the current style, with the
/// provided parameters overridden.
MarkdownStyleSheet 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,
TextStyle img,
double blockSpacing,
double listIndent,
double blockquotePadding,
Decoration blockquoteDecoration,
double codeblockPadding,
Decoration codeblockDecoration,
Decoration horizontalRuleDecoration,
double textScaleFactor,
}) {
return new MarkdownStyleSheet(
a: a ?? this.a,
p: p ?? this.p,
code: code ?? this.code,
h1: h1 ?? this.h1,
h2: h2 ?? this.h2,
h3: h3 ?? this.h3,
h4: h4 ?? this.h4,
h5: h5 ?? this.h5,
h6: h6 ?? this.h6,
em: em ?? this.em,
strong: strong ?? this.strong,
blockquote: blockquote ?? this.blockquote,
img: img ?? this.img,
blockSpacing: blockSpacing ?? this.blockSpacing,
listIndent: listIndent ?? this.listIndent,
blockquotePadding: blockquotePadding ?? this.blockquotePadding,
blockquoteDecoration: blockquoteDecoration ?? this.blockquoteDecoration,
codeblockPadding: codeblockPadding ?? this.codeblockPadding,
codeblockDecoration: codeblockDecoration ?? this.codeblockDecoration,
horizontalRuleDecoration: horizontalRuleDecoration ?? this.horizontalRuleDecoration,
textScaleFactor : textScaleFactor ?? this.textScaleFactor,
);
}
/// The [TextStyle] to use for `a` elements.
final TextStyle a;
/// The [TextStyle] to use for `p` elements.
final TextStyle p;
/// The [TextStyle] to use for `code` elements.
final TextStyle code;
/// The [TextStyle] to use for `h1` elements.
final TextStyle h1;
/// The [TextStyle] to use for `h2` elements.
final TextStyle h2;
/// The [TextStyle] to use for `h3` elements.
final TextStyle h3;
/// The [TextStyle] to use for `h4` elements.
final TextStyle h4;
/// The [TextStyle] to use for `h5` elements.
final TextStyle h5;
/// The [TextStyle] to use for `h6` elements.
final TextStyle h6;
/// The [TextStyle] to use for `em` elements.
final TextStyle em;
/// The [TextStyle] to use for `strong` elements.
final TextStyle strong;
/// The [TextStyle] to use for `blockquote` elements.
final TextStyle blockquote;
/// The [TextStyle] to use for `img` elements.
final TextStyle img;
/// The amount of vertical space to use between block-level elements.
final double blockSpacing;
/// The amount of horizontal space to indent list items.
final double listIndent;
/// The padding to use for `blockquote` elements.
final double blockquotePadding;
/// The decoration to use behind `blockquote` elements.
final Decoration blockquoteDecoration;
/// The padding to use for `pre` elements.
final double codeblockPadding;
/// The decoration to use behind for `pre` elements.
final Decoration codeblockDecoration;
/// The decoration to use for `hr` elements.
final Decoration horizontalRuleDecoration;
// The text scale factor to use in textual elements
final double textScaleFactor;
/// A [Map] from element name to the corresponding [TextStyle] object.
Map<String, TextStyle> get styles => _styles;
Map<String, TextStyle> _styles;
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other.runtimeType != MarkdownStyleSheet)
return false;
final MarkdownStyleSheet typedOther = other;
return typedOther.a == a
&& typedOther.p == p
&& typedOther.code == code
&& typedOther.h1 == h1
&& typedOther.h2 == h2
&& typedOther.h3 == h3
&& typedOther.h4 == h4
&& typedOther.h5 == h5
&& typedOther.h6 == h6
&& typedOther.em == em
&& typedOther.strong == strong
&& typedOther.blockquote == blockquote
&& typedOther.img == img
&& typedOther.blockSpacing == blockSpacing
&& typedOther.listIndent == listIndent
&& typedOther.blockquotePadding == blockquotePadding
&& typedOther.blockquoteDecoration == blockquoteDecoration
&& typedOther.codeblockPadding == codeblockPadding
&& typedOther.codeblockDecoration == codeblockDecoration
&& typedOther.horizontalRuleDecoration == horizontalRuleDecoration
&& typedOther.textScaleFactor == textScaleFactor;
}
@override
int get hashCode {
return hashList([
a,
p,
code,
h1,
h2,
h3,
h4,
h5,
h6,
em,
strong,
blockquote,
img,
blockSpacing,
listIndent,
blockquotePadding,
blockquoteDecoration,
codeblockPadding,
codeblockDecoration,
horizontalRuleDecoration,
textScaleFactor,
]);
}
}

View File

@ -0,0 +1,247 @@
// 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 'dart:io';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:meta/meta.dart';
import 'builder.dart';
import 'style_sheet.dart';
//
typedef Widget ItemDemoBuilder(Map<String, dynamic> attrs);
/// Signature for callbacks used by [MarkdownWidget] when the user taps a link.
///
/// Used by [MarkdownWidget.onTapLink].
typedef void MarkdownTapLinkCallback(String href);
/// Creates a format [TextSpan] given a string.
///
/// Used by [MarkdownWidget] to highlight the contents of `pre` elements.
abstract class SyntaxHighlighter { // ignore: one_member_abstracts
/// Returns the formated [TextSpan] for the given string.
TextSpan format(String source);
}
/// A base class for widgets that parse and display Markdown.
///
/// Supports all standard Markdown from the original
/// [Markdown specification](https://daringfireball.net/projects/markdown/).
///
/// See also:
///
/// * [Markdown], which is a scrolling container of Markdown.
/// * [MarkdownBody], which is a non-scrolling container of Markdown.
/// * <https://daringfireball.net/projects/markdown/>
abstract class MarkdownWidget extends StatefulWidget {
/// Creates a widget that parses and displays Markdown.
///
/// The [data] argument must not be null.
const MarkdownWidget({
Key key,
@required this.data,
this.styleSheet,
this.syntaxHighlighter,
this.onTapLink,
this.imageDirectory,
this.demoBuilder,
}) : assert(data != null),
super(key: key);
/// The Markdown to display.
final String data;
/// The styles to use when displaying the Markdown.
///
/// If null, the styles are inferred from the current [Theme].
final MarkdownStyleSheet styleSheet;
/// The syntax highlighter used to color text in `pre` elements.
///
/// If null, the [MarkdownStyleSheet.code] style is used for `pre` elements.
final SyntaxHighlighter syntaxHighlighter;
/// Called when the user taps a link.
final MarkdownTapLinkCallback onTapLink;
/// The base directory holding images referenced by Img tags with local file paths.
final Directory imageDirectory;
final ItemDemoBuilder demoBuilder;
/// Subclasses should override this function to display the given children,
/// which are the parsed representation of [data].
@protected
Widget build(BuildContext context, List<Widget> children);
@override
_MarkdownWidgetState createState() => new _MarkdownWidgetState();
}
class DemosSyntax extends md.InlineSyntax {
DemosSyntax() : super('\\[demo:([a-z0-9_+-]+)\\]');
bool onMatch(parser, match) {
var anchor = new md.Element.empty('demo');
anchor.attributes['id'] = match[1];
parser.addNode(anchor);
return true;
}
}
class _MarkdownWidgetState extends State<MarkdownWidget> implements MarkdownBuilderDelegate {
List<Widget> _children;
final List<GestureRecognizer> _recognizers = <GestureRecognizer>[];
@override
void didChangeDependencies() {
_parseMarkdown();
super.didChangeDependencies();
}
@override
void didUpdateWidget(MarkdownWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.data != oldWidget.data
|| widget.styleSheet != oldWidget.styleSheet)
_parseMarkdown();
}
@override
void dispose() {
_disposeRecognizers();
super.dispose();
}
void _parseMarkdown() {
final MarkdownStyleSheet styleSheet = widget.styleSheet ?? new MarkdownStyleSheet.fromTheme(Theme.of(context));
_disposeRecognizers();
// TODO: This can be optimized by doing the split and removing \r at the same time
final List<String> lines = widget.data.replaceAll('\r\n', '\n').split('\n');
final md.ExtensionSet extens = new md.ExtensionSet([
md.FencedCodeBlockSyntax()
], [
new DemosSyntax(),
new md.InlineHtmlSyntax(),
]);
final md.Document document = new md.Document(encodeHtml: false, extensionSet: extens);
final MarkdownBuilder builder = new MarkdownBuilder(
delegate: this,
styleSheet: styleSheet,
imageDirectory: widget.imageDirectory,
demoParser: widget.demoBuilder
);
_children = builder.build(document.parseLines(lines));
}
void _disposeRecognizers() {
if (_recognizers.isEmpty)
return;
final List<GestureRecognizer> localRecognizers = new List<GestureRecognizer>.from(_recognizers);
_recognizers.clear();
for (GestureRecognizer recognizer in localRecognizers)
recognizer.dispose();
}
@override
GestureRecognizer createLink(String href) {
final TapGestureRecognizer recognizer = new TapGestureRecognizer()
..onTap = () {
if (widget.onTapLink != null)
widget.onTapLink(href);
};
_recognizers.add(recognizer);
return recognizer;
}
@override
TextSpan formatText(MarkdownStyleSheet styleSheet, String code) {
if (widget.syntaxHighlighter != null)
return widget.syntaxHighlighter.format(code);
return new TextSpan(style: styleSheet.code, text: code);
}
@override
Widget build(BuildContext context) => widget.build(context, _children);
}
/// A non-scrolling widget that parses and displays Markdown.
///
/// Supports all standard Markdown from the original
/// [Markdown specification](https://daringfireball.net/projects/markdown/).
///
/// See also:
///
/// * [Markdown], which is a scrolling container of Markdown.
/// * <https://daringfireball.net/projects/markdown/>
class MarkdownBody extends MarkdownWidget {
/// Creates a non-scrolling widget that parses and displays Markdown.
const MarkdownBody({
Key key,
String data,
MarkdownStyleSheet styleSheet,
SyntaxHighlighter syntaxHighlighter,
MarkdownTapLinkCallback onTapLink,
Directory imageDirectory,
ItemDemoBuilder demoBuilder,
}) : super(
key: key,
data: data,
styleSheet: styleSheet,
syntaxHighlighter: syntaxHighlighter,
onTapLink: onTapLink,
imageDirectory: imageDirectory,
demoBuilder: demoBuilder
);
@override
Widget build(BuildContext context, List<Widget> children) {
if (children.length == 1)
return children.single;
return new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: children,
);
}
}
/// A scrolling widget that parses and displays Markdown.
///
/// Supports all standard Markdown from the original
/// [Markdown specification](https://daringfireball.net/projects/markdown/).
///
/// See also:
///
/// * [MarkdownBody], which is a non-scrolling container of Markdown.
/// * <https://daringfireball.net/projects/markdown/>
class Markdown extends MarkdownWidget {
/// Creates a scrolling widget that parses and displays Markdown.
const Markdown({
Key key,
String data,
MarkdownStyleSheet styleSheet,
SyntaxHighlighter syntaxHighlighter,
MarkdownTapLinkCallback onTapLink,
Directory imageDirectory,
this.padding: const EdgeInsets.all(16.0),
}) : super(
key: key,
data: data,
styleSheet: styleSheet,
syntaxHighlighter: syntaxHighlighter,
onTapLink: onTapLink,
imageDirectory: imageDirectory,
);
/// The amount of space by which to inset the children.
final EdgeInsets padding;
@override
Widget build(BuildContext context, List<Widget> children) {
return new ListView(padding: padding, children: children);
}
}

View File

@ -1,9 +0,0 @@
{
"name": "page",
"screenShot": "",
"author":"snfan",
"email": "hanxu317@qq.com",
"desc": "desc",
"id": "facca78e_32ae_4241_9c8a_5c9e1f92b096"
}

View File

@ -1,18 +0,0 @@
String getMd() {
return """
# page123
this is page markdown for test 123
you can load demo like this 123an load demo like this 123an load demo like this 123
123123
```
[demo: xxxid]
```
""";
}

View File

@ -1,11 +0,0 @@
# page123
this is page markdown for test 123
you can load demo like this 123an load demo like this 123an load demo like this 123
123123
```
[demo: xxxid]
```