Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
b0542344
Commit
b0542344
authored
Mar 10, 2016
by
Viktor Lidholt
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2585 from vlidholt/master
Initial support for links in markdown
parents
83caeb35
800e2558
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
124 additions
and
37 deletions
+124
-37
demo.dart
packages/flutter_markdown/example/demo.dart
+3
-0
markdown.dart
packages/flutter_markdown/lib/src/markdown.dart
+12
-6
markdown_raw.dart
packages/flutter_markdown/lib/src/markdown_raw.dart
+94
-20
flutter_markdown_test.dart
packages/flutter_markdown/test/flutter_markdown_test.dart
+15
-11
No files found.
packages/flutter_markdown/example/demo.dart
View file @
b0542344
...
...
@@ -15,6 +15,9 @@ Style text as _italic_, __bold__, or `inline code`.
- To better clarify
- Your points
## Links
You can use [hyperlinks](hyperlink) in markdown
## Code blocks
Formatted Dart code looks really pretty too. This is an example of how to create your own Markdown widget:
...
...
packages/flutter_markdown/lib/src/markdown.dart
View file @
b0542344
...
...
@@ -25,22 +25,26 @@ class Markdown extends MarkdownRaw {
Markdown
({
String
data
,
SyntaxHighlighter
syntaxHighlighter
,
MarkdownStyle
markdownStyle
MarkdownStyle
markdownStyle
,
MarkdownLinkCallback
onTapLink
})
:
super
(
data:
data
,
syntaxHighlighter:
syntaxHighlighter
,
markdownStyle:
markdownStyle
markdownStyle:
markdownStyle
,
onTapLink:
onTapLink
);
MarkdownBody
createMarkdownBody
({
String
data
,
MarkdownStyle
markdownStyle
,
SyntaxHighlighter
syntaxHighlighter
SyntaxHighlighter
syntaxHighlighter
,
MarkdownLinkCallback
onTapLink
})
{
return
new
MarkdownBody
(
data:
data
,
markdownStyle:
markdownStyle
,
syntaxHighlighter:
syntaxHighlighter
syntaxHighlighter:
syntaxHighlighter
,
onTapLink:
onTapLink
);
}
}
...
...
@@ -71,11 +75,13 @@ class MarkdownBody extends MarkdownBodyRaw {
MarkdownBody
({
String
data
,
SyntaxHighlighter
syntaxHighlighter
,
MarkdownStyle
markdownStyle
MarkdownStyle
markdownStyle
,
MarkdownLinkCallback
onTapLink
})
:
super
(
data:
data
,
syntaxHighlighter:
syntaxHighlighter
,
markdownStyle:
markdownStyle
markdownStyle:
markdownStyle
,
onTapLink:
onTapLink
);
MarkdownStyle
createDefaultStyle
(
BuildContext
context
)
{
...
...
packages/flutter_markdown/lib/src/markdown_raw.dart
View file @
b0542344
...
...
@@ -4,8 +4,11 @@
import
'package:markdown/markdown.dart'
as
md
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/gestures.dart'
;
import
'markdown_style_raw.dart'
;
typedef
void
MarkdownLinkCallback
(
String
href
);
/// A [Widget] that renders markdown formatted text. It supports all standard
/// markdowns from the original markdown specification found here:
...
...
@@ -26,7 +29,8 @@ class MarkdownRaw extends StatelessComponent {
this
.
data
,
this
.
markdownStyle
,
this
.
syntaxHighlighter
,
this
.
padding
:
const
EdgeDims
.
all
(
16.0
)
this
.
padding
:
const
EdgeDims
.
all
(
16.0
),
this
.
onTapLink
});
/// Markdown styled text
...
...
@@ -41,6 +45,9 @@ class MarkdownRaw extends StatelessComponent {
/// Padding used
final
EdgeDims
padding
;
/// Callback when a link is tapped
final
MarkdownLinkCallback
onTapLink
;
Widget
build
(
BuildContext
context
)
{
return
new
ScrollableViewport
(
child:
new
Padding
(
...
...
@@ -48,7 +55,8 @@ class MarkdownRaw extends StatelessComponent {
child:
createMarkdownBody
(
data:
data
,
markdownStyle:
markdownStyle
,
syntaxHighlighter:
syntaxHighlighter
syntaxHighlighter:
syntaxHighlighter
,
onTapLink:
onTapLink
)
)
);
...
...
@@ -57,12 +65,14 @@ class MarkdownRaw extends StatelessComponent {
MarkdownBodyRaw
createMarkdownBody
({
String
data
,
MarkdownStyleRaw
markdownStyle
,
SyntaxHighlighter
syntaxHighlighter
SyntaxHighlighter
syntaxHighlighter
,
MarkdownLinkCallback
onTapLink
})
{
return
new
MarkdownBodyRaw
(
data:
data
,
markdownStyle:
markdownStyle
,
syntaxHighlighter:
syntaxHighlighter
syntaxHighlighter:
syntaxHighlighter
,
onTapLink:
onTapLink
);
}
}
...
...
@@ -94,7 +104,8 @@ class MarkdownBodyRaw extends StatefulComponent {
MarkdownBodyRaw
({
this
.
data
,
this
.
markdownStyle
,
this
.
syntaxHighlighter
this
.
syntaxHighlighter
,
this
.
onTapLink
});
/// Markdown styled text
...
...
@@ -106,6 +117,9 @@ class MarkdownBodyRaw extends StatefulComponent {
/// The syntax highlighter used to color text in code blocks
final
SyntaxHighlighter
syntaxHighlighter
;
/// Callback when a link is tapped
final
MarkdownLinkCallback
onTapLink
;
_MarkdownBodyRawState
createState
()
=>
new
_MarkdownBodyRawState
();
MarkdownStyleRaw
createDefaultStyle
(
BuildContext
context
)
=>
null
;
...
...
@@ -119,10 +133,23 @@ class _MarkdownBodyRawState extends State<MarkdownBodyRaw> {
MarkdownStyleRaw
markdownStyle
=
config
.
markdownStyle
??
config
.
createDefaultStyle
(
context
);
SyntaxHighlighter
syntaxHighlighter
=
config
.
syntaxHighlighter
??
new
_DefaultSyntaxHighlighter
(
markdownStyle
.
code
);
_cachedBlocks
=
_blocksFromMarkup
(
config
.
data
,
markdownStyle
,
syntaxHighlighter
);
_linkHandler
=
new
_LinkHandler
(
config
.
onTapLink
);
// TODO: This can be optimized by doing the split and removing \r at the same time
List
<
String
>
lines
=
config
.
data
.
replaceAll
(
'
\r\n
'
,
'
\n
'
).
split
(
'
\n
'
);
md
.
Document
document
=
new
md
.
Document
();
_Renderer
renderer
=
new
_Renderer
();
_cachedBlocks
=
renderer
.
render
(
document
.
parseLines
(
lines
),
markdownStyle
,
syntaxHighlighter
,
_linkHandler
);
}
void
dispose
()
{
_linkHandler
.
dispose
();
super
.
dispose
();
}
List
<
_Block
>
_cachedBlocks
;
_LinkHandler
_linkHandler
;
Widget
build
(
BuildContext
context
)
{
List
<
Widget
>
blocks
=
<
Widget
>[];
...
...
@@ -137,23 +164,15 @@ class _MarkdownBodyRawState extends State<MarkdownBodyRaw> {
}
}
List
<
_Block
>
_blocksFromMarkup
(
String
data
,
MarkdownStyleRaw
markdownStyle
,
SyntaxHighlighter
syntaxHighlighter
)
{
// TODO: This can be optimized by doing the split and removing \r at the same time
List
<
String
>
lines
=
data
.
replaceAll
(
'
\r\n
'
,
'
\n
'
).
split
(
'
\n
'
);
md
.
Document
document
=
new
md
.
Document
();
_Renderer
renderer
=
new
_Renderer
();
return
renderer
.
render
(
document
.
parseLines
(
lines
),
markdownStyle
,
syntaxHighlighter
);
}
class
_Renderer
implements
md
.
NodeVisitor
{
List
<
_Block
>
render
(
List
<
md
.
Node
>
nodes
,
MarkdownStyleRaw
markdownStyle
,
SyntaxHighlighter
syntaxHighlighter
)
{
List
<
_Block
>
render
(
List
<
md
.
Node
>
nodes
,
MarkdownStyleRaw
markdownStyle
,
SyntaxHighlighter
syntaxHighlighter
,
_LinkHandler
linkHandler
)
{
assert
(
markdownStyle
!=
null
);
_blocks
=
<
_Block
>[];
_listIndents
=
<
String
>[];
_markdownStyle
=
markdownStyle
;
_syntaxHighlighter
=
syntaxHighlighter
;
_linkHandler
=
linkHandler
;
for
(
final
md
.
Node
node
in
nodes
)
{
node
.
accept
(
this
);
...
...
@@ -166,6 +185,7 @@ class _Renderer implements md.NodeVisitor {
List
<
String
>
_listIndents
;
MarkdownStyleRaw
_markdownStyle
;
SyntaxHighlighter
_syntaxHighlighter
;
_LinkHandler
_linkHandler
;
void
visitText
(
md
.
Text
text
)
{
_MarkdownNodeList
topList
=
_currentBlock
.
stack
.
last
;
...
...
@@ -191,8 +211,13 @@ class _Renderer implements md.NodeVisitor {
_Block
newBlock
=
new
_Block
(
element
.
tag
,
element
.
attributes
,
_markdownStyle
,
new
List
<
String
>.
from
(
_listIndents
),
blockList
.
length
);
blockList
.
add
(
newBlock
);
}
else
{
_LinkInfo
linkInfo
=
null
;
if
(
element
.
tag
==
'a'
)
{
linkInfo
=
_linkHandler
.
createLinkInfo
(
element
.
attributes
[
'href'
]);
}
TextStyle
style
=
_markdownStyle
.
styles
[
element
.
tag
]
??
new
TextStyle
();
List
<
_MarkdownNode
>
styleElement
=
<
_MarkdownNode
>[
new
_MarkdownNodeTextStyle
(
style
)];
List
<
_MarkdownNode
>
styleElement
=
<
_MarkdownNode
>[
new
_MarkdownNodeTextStyle
(
style
,
linkInfo
)];
_currentBlock
.
stack
.
add
(
new
_MarkdownNodeList
(
styleElement
));
}
return
true
;
...
...
@@ -260,8 +285,9 @@ class _MarkdownNodeList extends _MarkdownNode {
}
class
_MarkdownNodeTextStyle
extends
_MarkdownNode
{
_MarkdownNodeTextStyle
(
this
.
style
);
_MarkdownNodeTextStyle
(
this
.
style
,
[
this
.
linkInfo
=
null
]
);
TextStyle
style
;
_LinkInfo
linkInfo
;
}
class
_MarkdownNodeString
extends
_MarkdownNode
{
...
...
@@ -325,7 +351,8 @@ class _Block {
children:
subWidgets
);
}
else
{
contents
=
new
RichText
(
text:
_stackToTextSpan
(
new
_MarkdownNodeList
(
stack
)));
TextSpan
span
=
_stackToTextSpan
(
new
_MarkdownNodeList
(
stack
));
contents
=
new
RichText
(
text:
span
);
if
(
listIndents
.
length
>
0
)
{
Widget
bullet
;
...
...
@@ -384,13 +411,23 @@ class _Block {
if
(
stack
is
_MarkdownNodeList
)
{
List
<
_MarkdownNode
>
list
=
stack
.
list
;
_MarkdownNodeTextStyle
styleNode
=
list
[
0
];
_LinkInfo
linkInfo
=
styleNode
.
linkInfo
;
TextStyle
style
=
styleNode
.
style
;
List
<
TextSpan
>
children
=
<
TextSpan
>[];
for
(
int
i
=
1
;
i
<
list
.
length
;
i
++)
{
children
.
add
(
_stackToTextSpan
(
list
[
i
]));
}
return
new
TextSpan
(
style:
style
,
children:
children
);
String
text
=
null
;
if
(
children
.
length
==
1
&&
_isPlainText
(
children
[
0
]))
{
text
=
children
[
0
].
text
;
children
=
null
;
}
TapGestureRecognizer
recognizer
=
linkInfo
?.
recognizer
;
return
new
TextSpan
(
style:
style
,
children:
children
,
recognizer:
recognizer
,
text:
text
);
}
if
(
stack
is
_MarkdownNodeString
)
{
...
...
@@ -400,6 +437,10 @@ class _Block {
return
null
;
}
bool
_isPlainText
(
TextSpan
span
)
{
return
(
span
.
text
!=
null
&&
span
.
style
==
null
&&
span
.
recognizer
==
null
&&
span
.
children
==
null
);
}
Widget
_buildImage
(
BuildContext
context
,
String
src
)
{
List
<
String
>
parts
=
src
.
split
(
'#'
);
if
(
parts
.
length
==
0
)
return
new
Container
();
...
...
@@ -419,6 +460,39 @@ class _Block {
}
}
class
_LinkInfo
{
_LinkInfo
(
this
.
href
,
this
.
recognizer
);
final
String
href
;
final
TapGestureRecognizer
recognizer
;
}
class
_LinkHandler
{
_LinkHandler
(
this
.
onTapLink
);
List
<
_LinkInfo
>
links
=
<
_LinkInfo
>[];
MarkdownLinkCallback
onTapLink
;
_LinkInfo
createLinkInfo
(
String
href
)
{
TapGestureRecognizer
recognizer
=
new
TapGestureRecognizer
();
recognizer
.
onTap
=
()
{
if
(
onTapLink
!=
null
)
onTapLink
(
href
);
};
_LinkInfo
linkInfo
=
new
_LinkInfo
(
href
,
recognizer
);
links
.
add
(
linkInfo
);
return
linkInfo
;
}
void
dispose
()
{
for
(
_LinkInfo
linkInfo
in
links
)
{
linkInfo
.
recognizer
.
dispose
();
}
}
}
abstract
class
SyntaxHighlighter
{
TextSpan
format
(
String
source
);
}
...
...
packages/flutter_markdown/test/flutter_markdown_test.dart
View file @
b0542344
import
'package:flutter_markdown/flutter_markdown.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:test/test.dart'
;
import
'package:flutter/material.dart'
;
...
...
@@ -9,13 +10,9 @@ void main() {
testWidgets
((
WidgetTester
tester
)
{
tester
.
pumpWidget
(
new
MarkdownBody
(
data:
"Hello"
));
Element
textElement
=
tester
.
findElement
((
Element
element
)
=>
element
.
widget
is
RichText
);
RichText
textWidget
=
textElement
.
widget
;
TextSpan
textSpan
=
textWidget
.
text
;
List
<
Element
>
elements
=
_listElements
(
tester
);
_expectWidgetTypes
(
elements
,
<
Type
>[
MarkdownBody
,
Column
,
Container
,
Padding
,
RichText
]);
expect
(
textSpan
.
children
[
0
].
text
,
equals
(
"Hello"
)
);
_expectTextStrings
(
elements
,
<
String
>[
"Hello"
]
);
});
});
...
...
@@ -23,13 +20,9 @@ void main() {
testWidgets
((
WidgetTester
tester
)
{
tester
.
pumpWidget
(
new
MarkdownBody
(
data:
"# Header"
));
Element
textElement
=
tester
.
findElement
((
Element
element
)
=>
element
.
widget
is
RichText
);
RichText
textWidget
=
textElement
.
widget
;
TextSpan
textSpan
=
textWidget
.
text
;
List
<
Element
>
elements
=
_listElements
(
tester
);
_expectWidgetTypes
(
elements
,
<
Type
>[
MarkdownBody
,
Column
,
Container
,
Padding
,
RichText
]);
expect
(
textSpan
.
children
[
0
].
text
,
equals
(
"Header"
)
);
_expectTextStrings
(
elements
,
<
String
>[
"Header"
]
);
});
});
...
...
@@ -79,7 +72,6 @@ void main() {
tester
.
pumpWidget
(
new
Markdown
(
data:
""
));
List
<
Element
>
elements
=
_listElements
(
tester
);
for
(
Element
element
in
elements
)
print
(
"e:
$element
"
);
_expectWidgetTypes
(
elements
,
<
Type
>[
Markdown
,
ScrollableViewport
,
...
...
@@ -90,6 +82,18 @@ void main() {
]);
});
});
test
(
"Links"
,
()
{
testWidgets
((
WidgetTester
tester
)
{
tester
.
pumpWidget
(
new
Markdown
(
data:
"[Link Text](href)"
));
Element
textElement
=
tester
.
findElement
((
Element
element
)
=>
element
.
widget
is
RichText
);
RichText
textWidget
=
textElement
.
widget
;
TextSpan
span
=
textWidget
.
text
;
expect
(
span
.
children
[
0
].
recognizer
.
runtimeType
,
equals
(
TapGestureRecognizer
));
});
});
}
List
<
Element
>
_listElements
(
WidgetTester
tester
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment