mirror of
https://github.com/alibaba/flutter-go.git
synced 2025-07-15 03:04:25 +08:00
引入三方flutter_markdown
gi
This commit is contained in:
27
lib/components/flutter_markdown/LICENSE
Normal file
27
lib/components/flutter_markdown/LICENSE
Normal 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.
|
39
lib/components/flutter_markdown/README.md
Normal file
39
lib/components/flutter_markdown/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Flutter Markdown
|
||||||
|
[](https://pub.dartlang.org/packages/flutter_markdown)
|
||||||
|
[](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`.
|
10
lib/components/flutter_markdown/lib/flutter_markdown.dart
Normal file
10
lib/components/flutter_markdown/lib/flutter_markdown.dart
Normal 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';
|
376
lib/components/flutter_markdown/lib/src/builder.dart
Normal file
376
lib/components/flutter_markdown/lib/src/builder.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
307
lib/components/flutter_markdown/lib/src/style_sheet.dart
Normal file
307
lib/components/flutter_markdown/lib/src/style_sheet.dart
Normal 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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
247
lib/components/flutter_markdown/lib/src/widget.dart
Normal file
247
lib/components/flutter_markdown/lib/src/widget.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "page",
|
|
||||||
"screenShot": "",
|
|
||||||
"author":"snfan",
|
|
||||||
"email": "hanxu317@qq.com",
|
|
||||||
"desc": "desc",
|
|
||||||
"id": "facca78e_32ae_4241_9c8a_5c9e1f92b096"
|
|
||||||
}
|
|
||||||
|
|
@ -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]
|
|
||||||
```
|
|
||||||
|
|
||||||
""";
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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]
|
|
||||||
```
|
|
||||||
|
|
Reference in New Issue
Block a user