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