Toggle navigation
Toggle navigation
This project
Loading...
Sign in
flutter_package
/
gpt_markdown
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
saminsohag
2024-12-20 21:30:36 +0600
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
11ce4c5507c4cb3a5b7da49f232b349fd0816ca6
11ce4c55
1 parent
caa10053
new release
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
846 additions
and
371 deletions
CHANGELOG.md
README.md
example/.gitignore
example/example.md
example/lib/main.dart
example/macos/Runner/AppDelegate.swift
example/pubspec.lock
lib/custom_widgets/code_field.dart
lib/custom_widgets/link_button.dart
lib/custom_widgets/selectable_adapter.dart
lib/gpt_markdown.dart
lib/markdown_component.dart
lib/md_widget.dart
lib/theme.dart
pubspec.yaml
CHANGELOG.md
View file @
11ce4c5
## 1.0.0
*
`TexMarkdown` is renamed to `GptMarkdown`
.
*
`h1` to `h6` style added to `GptMarkdownThemeData`
class.
*
`hrLineThickness` value added to `GptMarkdownThemeData`
class.
*
`hrLineColor` Color added to `GptMarkdownThemeData`
class.
*
`linkColor` Color added to `GptMarkdownThemeData`
class.
*
`linkHoverColor` Color added to `GptMarkdownThemeData`
class.
*
Indentation improved.
*
Math equations are now default selectable.
*
`SelectableAdapter`
Widget added to make any widget selectable.
## 0.1.15
*
`CodeBlock` is moved out of `gpt_markdown.dart`
library.
## 0.1.14
*
C
na
ged
`withOpacity` to `withAlpha` in `theme.dart`
for highlightColor.
*
C
han
ged
`withOpacity` to `withAlpha` in `theme.dart`
for highlightColor.
## 0.1.13
...
...
README.md
View file @
11ce4c5
...
...
@@ -81,7 +81,7 @@ Check the documentation [here.](https://github.com/saminsohag/flutter_packages/t
import
'package:flutter/material.dart'
;
import
'package:gpt_markdown/gpt_markdown.dart'
;
return
Tex
Markdown
(
return
Gpt
Markdown
(
'''
* This is a unordered list.
'''
,
...
...
example/.gitignore
View file @
11ce4c5
...
...
@@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
...
...
example/example.md
View file @
11ce4c5
...
...
@@ -48,7 +48,7 @@ class _MyHomePageState extends State<MyHomePage> {
AnimatedBuilder
(
animation:
_controller
,
builder:
(
context
,
_
)
{
return
Tex
Markdown
(
return
Gpt
Markdown
(
_controller
.
text
,
style:
const
TextStyle
(
color:
Colors
.
red
,
...
...
@@ -72,4 +72,4 @@ class _MyHomePageState extends State<MyHomePage> {
}
}
```
\ No newline at end of file
```
...
...
example/lib/main.dart
View file @
11ce4c5
...
...
@@ -30,16 +30,24 @@ class _MyAppState extends State<MyApp> {
useMaterial3:
true
,
brightness:
Brightness
.
light
,
colorSchemeSeed:
Colors
.
blue
,
extensions:
[
GptMarkdownThemeData
(
brightness:
Brightness
.
light
,
highlightColor:
Colors
.
red
,
),
],
),
darkTheme:
ThemeData
(
useMaterial3:
true
,
brightness:
Brightness
.
dark
,
colorSchemeSeed:
Colors
.
blue
,
extensions:
[
GptMarkdownThemeData
(
highlightColor:
Colors
.
red
,
),
]),
useMaterial3:
true
,
brightness:
Brightness
.
dark
,
colorSchemeSeed:
Colors
.
blue
,
extensions:
[
GptMarkdownThemeData
(
brightness:
Brightness
.
dark
,
highlightColor:
Colors
.
red
,
),
],
),
home:
MyHomePage
(
title:
'GptMarkdown'
,
onPressed:
()
{
...
...
@@ -170,7 +178,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
Icons.select_all_outlined,
color: selectable
? Theme.of(context).colorScheme.onSurfaceVariant
: Theme.of(context).colorScheme.onSurface.withOpacity(0.38),
: Theme.of(context)
.colorScheme
.onSurface
.withValues(alpha: 0.38),
),
),
IconButton(
...
...
@@ -243,7 +254,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
// ),
child: Builder(
builder: (context) {
Widget child =
Tex
Markdown(
Widget child =
Gpt
Markdown(
_controller.text,
textDirection: _direction,
onLinkTab: (url, title) {
...
...
@@ -301,7 +312,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex
..insert(1, "|---|");
tableString =
tableStringList.join("
\n
");
return
Tex
Markdown(tableString);
return
Gpt
Markdown(tableString);
}
var controller = ScrollController();
Widget child = Math.tex(
...
...
example/macos/Runner/AppDelegate.swift
View file @
11ce4c5
...
...
@@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
override
func
applicationShouldTerminateAfterLastWindowClosed
(
_
sender
:
NSApplication
)
->
Bool
{
return
true
}
override
func
applicationSupportsSecureRestorableState
(
_
app
:
NSApplication
)
->
Bool
{
return
true
}
}
...
...
example/pubspec.lock
View file @
11ce4c5
...
...
@@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: args
sha256:
"7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
sha256:
bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
url: "https://pub.dev"
source: hosted
version: "2.
5
.0"
version: "2.
6
.0"
async:
dependency: transitive
description:
...
...
@@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: collection
sha256:
ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
sha256:
a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
url: "https://pub.dev"
source: hosted
version: "1.1
8
.0"
version: "1.1
9
.0"
cross_file:
dependency: transitive
description:
...
...
@@ -98,18 +98,18 @@ packages:
dependency: "direct main"
description:
name: flutter_math_fork
sha256: "
94bee4642892a94939af0748c6a7de0ff8318feee588379dcdfea7dc5cba06c8
"
sha256: "
284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac
"
url: "https://pub.dev"
source: hosted
version: "0.7.
2
"
version: "0.7.
3
"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
sha256: "
7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2
"
sha256: "
54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123
"
url: "https://pub.dev"
source: hosted
version: "2.0.1
0+1
"
version: "2.0.1
6
"
flutter_test:
dependency: "direct dev"
description: flutter
...
...
@@ -126,7 +126,7 @@ packages:
path: ".."
relative: true
source: path
version: "
0.1.14
"
version: "
1.0.0
"
http:
dependency: transitive
description:
...
...
@@ -139,26 +139,26 @@ packages:
dependency: transitive
description:
name: http_parser
sha256: "
2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b
"
sha256: "
76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360
"
url: "https://pub.dev"
source: hosted
version: "4.
0.2
"
version: "4.
1.1
"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "
3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05
"
sha256: "
7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06
"
url: "https://pub.dev"
source: hosted
version: "10.0.
5
"
version: "10.0.
7
"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "9
32549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806
"
sha256: "9
491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379
"
url: "https://pub.dev"
source: hosted
version: "3.0.
5
"
version: "3.0.
8
"
leak_tracker_testing:
dependency: transitive
description:
...
...
@@ -219,10 +219,10 @@ packages:
dependency: transitive
description:
name: path_parsing
sha256:
e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
sha256:
"883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
url: "https://pub.dev"
source: hosted
version: "1.
0.1
"
version: "1.
1.0
"
petitparser:
dependency: transitive
description:
...
...
@@ -243,7 +243,7 @@ packages:
dependency: transitive
description: flutter
source: sdk
version: "0.0.
99
"
version: "0.0.
0
"
source_span:
dependency: transitive
description:
...
...
@@ -256,10 +256,10 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: "
73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b
"
sha256: "
9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377
"
url: "https://pub.dev"
source: hosted
version: "1.1
1.1
"
version: "1.1
2.0
"
stream_channel:
dependency: transitive
description:
...
...
@@ -272,10 +272,10 @@ packages:
dependency: transitive
description:
name: string_scanner
sha256: "
556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde
"
sha256: "
688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3
"
url: "https://pub.dev"
source: hosted
version: "1.
2
.0"
version: "1.
3
.0"
term_glyph:
dependency: transitive
description:
...
...
@@ -288,10 +288,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "
5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb
"
sha256: "
664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c
"
url: "https://pub.dev"
source: hosted
version: "0.7.
2
"
version: "0.7.
3
"
tuple:
dependency: transitive
description:
...
...
@@ -304,34 +304,34 @@ packages:
dependency: transitive
description:
name: typed_data
sha256: f
acc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
sha256: f
9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.
3.2
"
version: "1.
4.0
"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "
32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3
"
sha256: "
27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7
"
url: "https://pub.dev"
source: hosted
version: "1.1.1
1+1
"
version: "1.1.1
5
"
vector_graphics_codec:
dependency: transitive
description:
name: vector_graphics_codec
sha256:
c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
sha256:
"2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb"
url: "https://pub.dev"
source: hosted
version: "1.1.1
1+1
"
version: "1.1.1
2
"
vector_graphics_compiler:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "1
2faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81
"
sha256: "1
b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad
"
url: "https://pub.dev"
source: hosted
version: "1.1.1
1+1
"
version: "1.1.1
6
"
vector_math:
dependency: transitive
description:
...
...
@@ -344,26 +344,26 @@ packages:
dependency: transitive
description:
name: vm_service
sha256:
"5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256:
f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
url: "https://pub.dev"
source: hosted
version: "14.
2.5
"
version: "14.
3.0
"
watcher:
dependency: "direct main"
description:
name: watcher
sha256: "
3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8
"
sha256: "
69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104
"
url: "https://pub.dev"
source: hosted
version: "1.1.
0
"
version: "1.1.
1
"
web:
dependency: transitive
description:
name: web
sha256:
"97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256:
cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "
0.5.1
"
version: "
1.1.0
"
xml:
dependency: transitive
description:
...
...
@@ -374,4 +374,4 @@ packages:
version: "6.5.0"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.
18.0-18.0.pre.54
"
flutter: ">=3.
22.0
"
...
...
lib/custom_widgets/code_field.dart
0 → 100644
View file @
11ce4c5
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
class
CodeField
extends
StatefulWidget
{
const
CodeField
({
super
.
key
,
required
this
.
name
,
required
this
.
codes
});
final
String
name
;
final
String
codes
;
@override
State
<
CodeField
>
createState
()
=>
_CodeFieldState
();
}
class
_CodeFieldState
extends
State
<
CodeField
>
{
bool
_copied
=
false
;
@override
Widget
build
(
BuildContext
context
)
{
return
Material
(
color:
Theme
.
of
(
context
).
colorScheme
.
onInverseSurface
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
[
Row
(
children:
[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
,
vertical:
8
),
child:
Text
(
widget
.
name
),
),
const
Spacer
(),
TextButton
.
icon
(
style:
TextButton
.
styleFrom
(
foregroundColor:
Theme
.
of
(
context
).
colorScheme
.
onSurface
,
textStyle:
const
TextStyle
(
fontWeight:
FontWeight
.
normal
,
),
),
onPressed:
()
async
{
await
Clipboard
.
setData
(
ClipboardData
(
text:
widget
.
codes
))
.
then
((
value
)
{
setState
(()
{
_copied
=
true
;
});
});
await
Future
.
delayed
(
const
Duration
(
seconds:
2
));
setState
(()
{
_copied
=
false
;
});
},
icon:
Icon
(
(
_copied
)
?
Icons
.
done
:
Icons
.
content_paste
,
size:
15
,
),
label:
Text
((
_copied
)
?
"Copied!"
:
"Copy code"
),
),
],
),
const
Divider
(
height:
1
,
),
SingleChildScrollView
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
all
(
16
),
child:
Text
(
widget
.
codes
,
),
),
],
),
);
}
}
...
...
lib/custom_widgets/link_button.dart
View file @
11ce4c5
...
...
@@ -8,11 +8,15 @@ class LinkButton extends StatefulWidget {
final
TextStyle
?
textStyle
;
final
String
?
url
;
final
GptMarkdownConfig
config
;
final
Color
color
;
final
Color
hoverColor
;
const
LinkButton
(
{
super
.
key
,
required
this
.
text
,
required
this
.
config
,
required
this
.
color
,
required
this
.
hoverColor
,
this
.
onPressed
,
this
.
textStyle
,
this
.
url
});
...
...
@@ -27,9 +31,9 @@ class _LinkButtonState extends State<LinkButton> {
@override
Widget
build
(
BuildContext
context
)
{
var
style
=
(
widget
.
config
.
style
??
const
TextStyle
()).
copyWith
(
color:
_isHovering
?
Colors
.
red
:
Colors
.
blue
,
color:
_isHovering
?
widget
.
hoverColor
:
widget
.
color
,
decoration:
TextDecoration
.
underline
,
decorationColor:
_isHovering
?
Colors
.
red
:
Colors
.
blue
,
decorationColor:
_isHovering
?
widget
.
hoverColor
:
widget
.
color
,
);
return
MouseRegion
(
cursor:
SystemMouseCursors
.
click
,
...
...
lib/custom_widgets/selectable_adapter.dart
0 → 100644
View file @
11ce4c5
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
class
SelectableAdapter
extends
StatelessWidget
{
const
SelectableAdapter
(
{
super
.
key
,
required
this
.
selectedText
,
required
this
.
child
});
final
Widget
child
;
final
String
selectedText
;
@override
Widget
build
(
BuildContext
context
)
{
final
SelectionRegistrar
?
registrar
=
SelectionContainer
.
maybeOf
(
context
);
if
(
registrar
==
null
)
{
return
child
;
}
return
MouseRegion
(
cursor:
SystemMouseCursors
.
text
,
child:
_SelectableAdapter
(
registrar:
registrar
,
selectedText:
selectedText
,
child:
child
,
),
);
}
}
class
_SelectableAdapter
extends
SingleChildRenderObjectWidget
{
const
_SelectableAdapter
({
required
this
.
registrar
,
required
Widget
child
,
required
this
.
selectedText
,
})
:
super
(
child:
child
);
final
SelectionRegistrar
registrar
;
final
String
selectedText
;
@override
_RenderSelectableAdapter
createRenderObject
(
BuildContext
context
)
{
return
_RenderSelectableAdapter
(
DefaultSelectionStyle
.
of
(
context
).
selectionColor
!,
selectedText
,
registrar
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderSelectableAdapter
renderObject
)
{
renderObject
..
selectionColor
=
DefaultSelectionStyle
.
of
(
context
).
selectionColor
!
..
registrar
=
registrar
;
}
}
class
_RenderSelectableAdapter
extends
RenderProxyBox
with
Selectable
,
SelectionRegistrant
{
String
selectionText
;
_RenderSelectableAdapter
(
Color
selectionColor
,
this
.
selectionText
,
SelectionRegistrar
registrar
,
)
:
_selectionColor
=
selectionColor
,
_geometry
=
ValueNotifier
<
SelectionGeometry
>(
_noSelection
)
{
this
.
registrar
=
registrar
;
_geometry
.
addListener
(
markNeedsPaint
);
}
static
const
SelectionGeometry
_noSelection
=
SelectionGeometry
(
status:
SelectionStatus
.
none
,
hasContent:
true
);
final
ValueNotifier
<
SelectionGeometry
>
_geometry
;
Color
get
selectionColor
=>
_selectionColor
;
late
Color
_selectionColor
;
set
selectionColor
(
Color
value
)
{
if
(
_selectionColor
==
value
)
{
return
;
}
_selectionColor
=
value
;
markNeedsPaint
();
}
// ValueListenable APIs
@override
void
addListener
(
VoidCallback
listener
)
=>
_geometry
.
addListener
(
listener
);
@override
void
removeListener
(
VoidCallback
listener
)
=>
_geometry
.
removeListener
(
listener
);
@override
SelectionGeometry
get
value
=>
_geometry
.
value
;
// Selectable APIs.
@override
List
<
Rect
>
get
boundingBoxes
=>
<
Rect
>[
paintBounds
];
// Adjust this value to enlarge or shrink the selection highlight.
static
const
double
_padding
=
0.0
;
Rect
_getSelectionHighlightRect
()
{
return
Rect
.
fromLTWH
(
0
-
_padding
,
0
-
_padding
,
size
.
width
+
_padding
*
2
,
size
.
height
+
_padding
*
2
);
}
Offset
?
_start
;
Offset
?
_end
;
void
_updateGeometry
()
{
if
(
_start
==
null
||
_end
==
null
)
{
_geometry
.
value
=
_noSelection
;
return
;
}
final
Rect
renderObjectRect
=
Rect
.
fromLTWH
(
0
,
0
,
size
.
width
,
size
.
height
);
final
Rect
selectionRect
=
Rect
.
fromPoints
(
_start
!,
_end
!);
if
(
renderObjectRect
.
intersect
(
selectionRect
).
isEmpty
)
{
_geometry
.
value
=
_noSelection
;
}
else
{
final
Rect
selectionRect
=
_getSelectionHighlightRect
();
final
SelectionPoint
firstSelectionPoint
=
SelectionPoint
(
localPosition:
selectionRect
.
bottomLeft
,
lineHeight:
selectionRect
.
size
.
height
,
handleType:
TextSelectionHandleType
.
left
,
);
final
SelectionPoint
secondSelectionPoint
=
SelectionPoint
(
localPosition:
selectionRect
.
bottomRight
,
lineHeight:
selectionRect
.
size
.
height
,
handleType:
TextSelectionHandleType
.
right
,
);
final
bool
isReversed
;
if
(
_start
!.
dy
>
_end
!.
dy
)
{
isReversed
=
true
;
}
else
if
(
_start
!.
dy
<
_end
!.
dy
)
{
isReversed
=
false
;
}
else
{
isReversed
=
_start
!.
dx
>
_end
!.
dx
;
}
_geometry
.
value
=
SelectionGeometry
(
status:
SelectionStatus
.
uncollapsed
,
hasContent:
true
,
startSelectionPoint:
isReversed
?
secondSelectionPoint
:
firstSelectionPoint
,
endSelectionPoint:
isReversed
?
firstSelectionPoint
:
secondSelectionPoint
,
selectionRects:
<
Rect
>[
selectionRect
],
);
}
}
@override
SelectionResult
dispatchSelectionEvent
(
SelectionEvent
event
)
{
SelectionResult
result
=
SelectionResult
.
none
;
switch
(
event
.
type
)
{
case
SelectionEventType
.
startEdgeUpdate
:
case
SelectionEventType
.
endEdgeUpdate
:
final
Rect
renderObjectRect
=
Rect
.
fromLTWH
(
0
,
0
,
size
.
width
,
size
.
height
);
// Normalize offset in case it is out side of the rect.
final
Offset
point
=
globalToLocal
((
event
as
SelectionEdgeUpdateEvent
).
globalPosition
);
final
Offset
adjustedPoint
=
SelectionUtils
.
adjustDragOffset
(
renderObjectRect
,
point
);
if
(
event
.
type
==
SelectionEventType
.
startEdgeUpdate
)
{
_start
=
adjustedPoint
;
}
else
{
_end
=
adjustedPoint
;
}
result
=
SelectionUtils
.
getResultBasedOnRect
(
renderObjectRect
,
point
);
case
SelectionEventType
.
clear
:
_start
=
_end
=
null
;
case
SelectionEventType
.
selectAll
:
case
SelectionEventType
.
selectWord
:
case
SelectionEventType
.
selectParagraph
:
_start
=
Offset
.
zero
;
_end
=
Offset
.
infinite
;
case
SelectionEventType
.
granularlyExtendSelection
:
result
=
SelectionResult
.
end
;
final
GranularlyExtendSelectionEvent
extendSelectionEvent
=
event
as
GranularlyExtendSelectionEvent
;
// Initialize the offset it there is no ongoing selection.
if
(
_start
==
null
||
_end
==
null
)
{
if
(
extendSelectionEvent
.
forward
)
{
_start
=
_end
=
Offset
.
zero
;
}
else
{
_start
=
_end
=
Offset
.
infinite
;
}
}
// Move the corresponding selection edge.
final
Offset
newOffset
=
extendSelectionEvent
.
forward
?
Offset
.
infinite
:
Offset
.
zero
;
if
(
extendSelectionEvent
.
isEnd
)
{
if
(
newOffset
==
_end
)
{
result
=
extendSelectionEvent
.
forward
?
SelectionResult
.
next
:
SelectionResult
.
previous
;
}
_end
=
newOffset
;
}
else
{
if
(
newOffset
==
_start
)
{
result
=
extendSelectionEvent
.
forward
?
SelectionResult
.
next
:
SelectionResult
.
previous
;
}
_start
=
newOffset
;
}
case
SelectionEventType
.
directionallyExtendSelection
:
result
=
SelectionResult
.
end
;
final
DirectionallyExtendSelectionEvent
extendSelectionEvent
=
event
as
DirectionallyExtendSelectionEvent
;
// Convert to local coordinates.
final
double
horizontalBaseLine
=
globalToLocal
(
Offset
(
event
.
dx
,
0
)).
dx
;
final
Offset
newOffset
;
final
bool
forward
;
switch
(
extendSelectionEvent
.
direction
)
{
case
SelectionExtendDirection
.
backward
:
case
SelectionExtendDirection
.
previousLine
:
forward
=
false
;
// Initialize the offset it there is no ongoing selection.
if
(
_start
==
null
||
_end
==
null
)
{
_start
=
_end
=
Offset
.
infinite
;
}
// Move the corresponding selection edge.
if
(
extendSelectionEvent
.
direction
==
SelectionExtendDirection
.
previousLine
||
horizontalBaseLine
<
0
)
{
newOffset
=
Offset
.
zero
;
}
else
{
newOffset
=
Offset
.
infinite
;
}
case
SelectionExtendDirection
.
nextLine
:
case
SelectionExtendDirection
.
forward
:
forward
=
true
;
// Initialize the offset it there is no ongoing selection.
if
(
_start
==
null
||
_end
==
null
)
{
_start
=
_end
=
Offset
.
zero
;
}
// Move the corresponding selection edge.
if
(
extendSelectionEvent
.
direction
==
SelectionExtendDirection
.
nextLine
||
horizontalBaseLine
>
size
.
width
)
{
newOffset
=
Offset
.
infinite
;
}
else
{
newOffset
=
Offset
.
zero
;
}
}
if
(
extendSelectionEvent
.
isEnd
)
{
if
(
newOffset
==
_end
)
{
result
=
forward
?
SelectionResult
.
next
:
SelectionResult
.
previous
;
}
_end
=
newOffset
;
}
else
{
if
(
newOffset
==
_start
)
{
result
=
forward
?
SelectionResult
.
next
:
SelectionResult
.
previous
;
}
_start
=
newOffset
;
}
}
_updateGeometry
();
return
result
;
}
// This method is called when users want to copy selected content in this
// widget into clipboard.
@override
SelectedContent
?
getSelectedContent
()
{
return
value
.
hasSelection
?
SelectedContent
(
plainText:
selectionText
)
:
null
;
}
LayerLink
?
_startHandle
;
LayerLink
?
_endHandle
;
@override
void
pushHandleLayers
(
LayerLink
?
startHandle
,
LayerLink
?
endHandle
)
{
if
(
_startHandle
==
startHandle
&&
_endHandle
==
endHandle
)
{
return
;
}
_startHandle
=
startHandle
;
_endHandle
=
endHandle
;
markNeedsPaint
();
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
super
.
paint
(
context
,
offset
);
if
(!
_geometry
.
value
.
hasSelection
)
{
return
;
}
// Draw the selection highlight.
final
Paint
selectionPaint
=
Paint
()
..
style
=
PaintingStyle
.
fill
..
color
=
_selectionColor
;
context
.
canvas
.
drawRect
(
_getSelectionHighlightRect
().
shift
(
offset
),
selectionPaint
);
// Push the layer links if any.
if
(
_startHandle
!=
null
)
{
context
.
pushLayer
(
LeaderLayer
(
link:
_startHandle
!,
offset:
offset
+
value
.
startSelectionPoint
!.
localPosition
,
),
(
PaintingContext
context
,
Offset
offset
)
{},
Offset
.
zero
,
);
}
if
(
_endHandle
!=
null
)
{
context
.
pushLayer
(
LeaderLayer
(
link:
_endHandle
!,
offset:
offset
+
value
.
endSelectionPoint
!.
localPosition
,
),
(
PaintingContext
context
,
Offset
offset
)
{},
Offset
.
zero
,
);
}
}
@override
void
dispose
()
{
_geometry
.
dispose
();
super
.
dispose
();
}
}
...
...
lib/gpt_markdown.dart
View file @
11ce4c5
...
...
@@ -4,14 +4,15 @@ import 'package:flutter/material.dart';
import
'package:gpt_markdown/custom_widgets/markdow_config.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_math_fork/flutter_math.dart'
;
import
'package:gpt_markdown/custom_widgets/custom_divider.dart'
;
import
'package:gpt_markdown/custom_widgets/custom_error_image.dart'
;
import
'package:gpt_markdown/custom_widgets/custom_rb_cb.dart'
;
import
'package:gpt_markdown/custom_widgets/selectable_adapter.dart'
;
import
'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart'
;
import
'dart:math'
;
import
'custom_widgets/code_field.dart'
;
import
'custom_widgets/link_button.dart'
;
part
'theme.dart'
;
...
...
@@ -19,8 +20,8 @@ part 'markdown_component.dart';
part
'md_widget.dart'
;
/// This widget create a full markdown widget as a column view.
class
TexMarkdown
extends
StatelessWidget
{
const
TexMarkdown
(
class
GptMarkdown
extends
StatelessWidget
{
const
GptMarkdown
(
this
.
data
,
{
super
.
key
,
this
.
style
,
...
...
lib/markdown_component.dart
View file @
11ce4c5
...
...
@@ -2,24 +2,24 @@ part of 'gpt_markdown.dart';
/// Markdown components
abstract
class
MarkdownComponent
{
static
List
<
MarkdownComponent
>
components
=
[
static
final
List
<
MarkdownComponent
>
components
=
[
CodeBlockMd
(),
NewLines
(),
TableMd
(),
HTag
(),
IndentMd
(),
UnOrderedList
(),
OrderedList
(),
RadioButtonMd
(),
CheckBoxMd
(),
HrLine
(),
IndentMd
(),
LatexMathMultyLine
(),
LatexMath
(),
ImageMd
(),
HighlightedText
(),
StrikeMd
(),
BoldMd
(),
ItalicMd
(),
LatexMathMultyLine
(),
LatexMath
(),
ATagMd
(),
SourceTag
(),
];
...
...
@@ -38,46 +38,42 @@ abstract class MarkdownComponent {
multiLine:
true
,
dotAll:
true
,
);
List
<
String
>
elements
=
[];
text
.
splitMapJoin
(
combinedRegex
,
onMatch:
(
p0
)
{
String
element
=
p0
[
0
]
??
""
;
elements
.
add
(
element
);
for
(
var
each
in
components
)
{
if
(
each
.
exp
.
hasMatch
(
element
))
{
if
(
each
is
InlineMd
)
{
if
(
each
.
inline
)
{
spans
.
add
(
each
.
span
(
context
,
element
,
config
,
));
}
else
{
if
(
each
is
BlockMd
)
{
spans
.
addAll
([
TextSpan
(
text:
"
\n
"
,
style:
TextStyle
(
fontSize:
0
,
height:
0
,
color:
config
.
style
?.
color
,
),
spans
.
addAll
([
TextSpan
(
text:
"
\n
"
,
style:
TextStyle
(
fontSize:
0
,
height:
0
,
color:
config
.
style
?.
color
,
),
each
.
span
(
context
,
element
,
config
,
),
TextSpan
(
text:
"
\n
"
,
style:
TextStyle
(
fontSize:
0
,
height:
0
,
color:
config
.
style
?.
color
,
),
),
each
.
span
(
context
,
element
,
config
,
),
TextSpan
(
text:
"
\n
"
,
style:
TextStyle
(
fontSize:
0
,
height:
0
,
color:
config
.
style
?.
color
,
),
]);
}
),
]);
}
return
""
;
}
...
...
@@ -125,18 +121,38 @@ abstract class InlineMd extends MarkdownComponent {
abstract
class
BlockMd
extends
MarkdownComponent
{
@override
bool
get
inline
=>
false
;
@override
RegExp
get
exp
=>
RegExp
(
r'^\ *?'
+
expString
,
dotAll:
true
,
multiLine:
true
,
);
String
get
expString
;
@override
InlineSpan
span
(
BuildContext
context
,
String
text
,
final
GptMarkdownConfig
config
,
)
{
var
matches
=
RegExp
(
r'^(?<spaces>\ \ +).*'
).
firstMatch
(
text
);
var
spaces
=
matches
?.
namedGroup
(
'spaces'
);
var
length
=
spaces
?.
length
??
0
;
var
child
=
build
(
context
,
text
,
config
,
);
if
(
length
>
0
)
{
child
=
UnorderedListView
(
spacing:
length
.
toDouble
()
*
6
,
child:
child
,
);
}
return
WidgetSpan
(
child:
build
(
context
,
text
,
config
,
),
child:
child
,
alignment:
PlaceholderAlignment
.
middle
,
);
}
...
...
@@ -151,57 +167,44 @@ abstract class BlockMd extends MarkdownComponent {
/// Heading component
class
HTag
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"^(#{1,6})\ (
[^\n]+?)$"
);
String
get
expString
=>
(
r"(?<hash>#{1,6})\ (?<data>
[^\n]+?)$"
);
@override
Widget
build
(
BuildContext
context
,
String
text
,
final
GptMarkdownConfig
config
,
)
{
var
match
=
exp
.
firstMatch
(
text
.
trim
());
var
theme
=
GptMarkdownTheme
.
of
(
context
);
var
match
=
this
.
exp
.
firstMatch
(
text
.
trim
());
var
conf
=
config
.
copyWith
(
style:
[
Theme
.
of
(
context
)
.
textTheme
.
headlineLarge
?.
copyWith
(
color:
config
.
style
?.
color
),
Theme
.
of
(
context
)
.
textTheme
.
headlineMedium
?.
copyWith
(
color:
config
.
style
?.
color
),
Theme
.
of
(
context
)
.
textTheme
.
headlineSmall
?.
copyWith
(
color:
config
.
style
?.
color
),
Theme
.
of
(
context
)
.
textTheme
.
titleLarge
?.
copyWith
(
color:
config
.
style
?.
color
),
Theme
.
of
(
context
)
.
textTheme
.
titleMedium
?.
copyWith
(
color:
config
.
style
?.
color
),
Theme
.
of
(
context
)
.
textTheme
.
titleSmall
?.
copyWith
(
color:
config
.
style
?.
color
),
][
match
![
1
]!.
length
-
1
]);
style:
[
theme
.
h1
,
theme
.
h2
,
theme
.
h3
,
theme
.
h4
,
theme
.
h5
,
theme
.
h6
,
][
match
![
1
]!.
length
-
1
]
?.
copyWith
(
color:
config
.
style
?.
color
,
),
);
return
config
.
getRich
(
TextSpan
(
children:
[
...(
MarkdownComponent
.
generate
(
context
,
"
${match
[2]
}
"
,
"
${match
.namedGroup('data')
}
"
,
conf
,
)),
if
(
match
[
1
]
!.
length
==
1
)
...[
if
(
match
.
namedGroup
(
'hash'
)
!.
length
==
1
)
...[
const
TextSpan
(
text:
"
\n
"
,
style:
TextStyle
(
fontSize:
0
,
height:
0
),
),
WidgetSpan
(
child:
CustomDivider
(
height:
2
,
height:
theme
.
hrLineThickness
,
color:
config
.
style
?.
color
??
Theme
.
of
(
context
).
colorScheme
.
outline
,
),
...
...
@@ -235,16 +238,18 @@ class NewLines extends InlineMd {
/// Horizontal line component
class
HrLine
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"^
(--)[-]+$"
);
String
get
expString
=>
(
r"
(--)[-]+$"
);
@override
Widget
build
(
BuildContext
context
,
String
text
,
final
GptMarkdownConfig
config
,
)
{
var
thickness
=
GptMarkdownTheme
.
of
(
context
).
hrLineThickness
;
var
color
=
GptMarkdownTheme
.
of
(
context
).
hrLineColor
;
return
CustomDivider
(
height:
2
,
color:
config
.
style
?.
color
??
Theme
.
of
(
context
).
colorScheme
.
outline
,
height:
thickness
,
color:
config
.
style
?.
color
??
color
,
);
}
}
...
...
@@ -252,7 +257,7 @@ class HrLine extends BlockMd {
/// Checkbox component
class
CheckBoxMd
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"^
\[(\x?)\]\ (\S[^\n]*?)$"
);
String
get
expString
=>
(
r"
\[(\x?)\]\ (\S[^\n]*?)$"
);
get
onLinkTab
=>
null
;
@override
...
...
@@ -261,7 +266,7 @@ class CheckBoxMd extends BlockMd {
String
text
,
final
GptMarkdownConfig
config
,
)
{
var
match
=
exp
.
firstMatch
(
text
.
trim
());
var
match
=
this
.
exp
.
firstMatch
(
text
.
trim
());
return
CustomCb
(
value:
(
"
${match?[1]}
"
==
"x"
),
textDirection:
config
.
textDirection
,
...
...
@@ -276,7 +281,7 @@ class CheckBoxMd extends BlockMd {
/// Radio Button component
class
RadioButtonMd
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"^
\((\x?)\)\ (\S[^\n]*)$"
);
String
get
expString
=>
(
r"
\((\x?)\)\ (\S[^\n]*)$"
);
get
onLinkTab
=>
null
;
@override
...
...
@@ -285,7 +290,7 @@ class RadioButtonMd extends BlockMd {
String
text
,
final
GptMarkdownConfig
config
,
)
{
var
match
=
exp
.
firstMatch
(
text
.
trim
());
var
match
=
this
.
exp
.
firstMatch
(
text
.
trim
());
return
CustomRb
(
value:
(
"
${match?[1]}
"
==
"x"
),
textDirection:
config
.
textDirection
,
...
...
@@ -298,36 +303,45 @@ class RadioButtonMd extends BlockMd {
}
/// Indent
class
IndentMd
extends
Block
Md
{
class
IndentMd
extends
Inline
Md
{
@override
RegExp
get
exp
=>
RegExp
(
r"^(\ \ \ \ +)([^\n]+)$"
);
get
onLinkTab
=>
null
;
RegExp
get
exp
=>
RegExp
(
r"^(\ +)(((?!\n\n).)+)$"
,
dotAll:
true
,
multiLine:
true
);
@override
Widget
build
(
InlineSpan
span
(
BuildContext
context
,
String
text
,
final
GptMarkdownConfig
config
,
)
{
[
r"\\\[(.*?)\\\]"
,
r"\\\((.*?)\\\)"
,
r"(?<!\\)\$((?:\\.|[^$])*?)\$(?!\\)"
,
].
join
(
"|"
);
var
match
=
exp
.
firstMatch
(
text
);
int
spaces
=
(
match
?[
1
]
??
""
).
length
;
return
UnorderedListView
(
bulletColor:
config
.
style
?.
color
,
padding:
spaces
*
5
,
bulletSize:
0
,
textDirection:
config
.
textDirection
,
child:
Text
.
rich
(
TextSpan
(
children:
MarkdownComponent
.
generate
(
context
,
"
${match?[2]}
"
,
config
,
var
data
=
"
${match?[2]}
"
.
trim
();
data
.
replaceAll
(
RegExp
(
r'\n\ *'
),
'
\n
'
).
trim
();
var
child
=
TextSpan
(
children:
MarkdownComponent
.
generate
(
context
,
data
,
config
,
),
);
if
(
spaces
<
4
)
{
return
child
;
}
return
TextSpan
(
children:
[
const
TextSpan
(
text:
'
\n
'
),
WidgetSpan
(
child:
UnorderedListView
(
bulletColor:
config
.
style
?.
color
,
padding:
spaces
*
5
,
bulletSize:
0
,
textDirection:
config
.
textDirection
,
child:
Text
.
rich
(
child
),
),
),
)),
const
TextSpan
(
text:
'
\n
'
),
],
);
}
}
...
...
@@ -335,7 +349,7 @@ class IndentMd extends BlockMd {
/// Unordered list component
class
UnOrderedList
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"^
(?:\-|\*)\ ([^\n]+)$"
);
String
get
expString
=>
(
r"
(?:\-|\*)\ ([^\n]+)$"
);
get
onLinkTab
=>
null
;
@override
...
...
@@ -344,7 +358,7 @@ class UnOrderedList extends BlockMd {
String
text
,
final
GptMarkdownConfig
config
,
)
{
var
match
=
exp
.
firstMatch
(
text
);
var
match
=
this
.
exp
.
firstMatch
(
text
);
return
UnorderedListView
(
bulletColor:
config
.
style
?.
color
??
DefaultTextStyle
.
of
(
context
).
style
.
color
,
...
...
@@ -365,7 +379,7 @@ class UnOrderedList extends BlockMd {
/// Ordered list component
class
OrderedList
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"^
([0-9]+\.)\ ([^\n]+)$"
);
String
get
expString
=>
(
r"
([0-9]+\.)\ ([^\n]+)$"
);
get
onLinkTab
=>
null
;
...
...
@@ -375,7 +389,7 @@ class OrderedList extends BlockMd {
String
text
,
final
GptMarkdownConfig
config
,
)
{
var
match
=
exp
.
firstMatch
(
text
.
trim
());
var
match
=
this
.
exp
.
firstMatch
(
text
.
trim
());
return
OrderedListView
(
no:
"
${match?[1]}
"
,
textDirection:
config
.
textDirection
,
...
...
@@ -508,10 +522,9 @@ class ItalicMd extends InlineMd {
class
LatexMathMultyLine
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})"
,
dotAll:
true
,
);
String
get
expString
=>
(
r"\\\[(((?!\n\n).)*)\\\]|(\\begin.*?\\end{.*?})"
);
@override
RegExp
get
exp
=>
RegExp
(
expString
,
dotAll:
true
,
multiLine:
true
);
@override
Widget
build
(
...
...
@@ -520,48 +533,50 @@ class LatexMathMultyLine extends BlockMd {
final
GptMarkdownConfig
config
,
)
{
var
p0
=
exp
.
firstMatch
(
text
.
trim
());
p0
?.
group
(
0
);
String
mathText
=
p0
?[
1
]
??
p0
?[
2
]
??
""
;
var
workaround
=
config
.
latexWorkaround
??
(
String
tex
)
=>
tex
;
var
builder
=
config
.
latexBuilder
??
(
BuildContext
context
,
String
tex
,
TextStyle
textStyle
,
bool
inline
)
=>
Math
.
tex
(
tex
,
textStyle:
textStyle
,
mathStyle:
MathStyle
.
display
,
textScaleFactor:
1
,
settings:
const
TexParserSettings
(
strict:
Strict
.
ignore
,
),
options:
MathOptions
(
sizeUnderTextStyle:
MathSize
.
large
,
color:
config
.
style
?.
color
??
Theme
.
of
(
context
).
colorScheme
.
onSurface
,
fontSize:
config
.
style
?.
fontSize
??
Theme
.
of
(
context
).
textTheme
.
bodyMedium
?.
fontSize
,
mathFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
SelectableAdapter
(
selectedText:
tex
,
child:
Math
.
tex
(
tex
,
textStyle:
textStyle
,
mathStyle:
MathStyle
.
display
,
textScaleFactor:
1
,
settings:
const
TexParserSettings
(
strict:
Strict
.
ignore
,
),
textFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
options:
MathOptions
(
sizeUnderTextStyle:
MathSize
.
large
,
color:
config
.
style
?.
color
??
Theme
.
of
(
context
).
colorScheme
.
onSurface
,
fontSize:
config
.
style
?.
fontSize
??
Theme
.
of
(
context
).
textTheme
.
bodyMedium
?.
fontSize
,
mathFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
),
textFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
),
style:
MathStyle
.
display
,
),
style:
MathStyle
.
display
,
onErrorFallback:
(
err
)
{
return
Text
(
workaround
(
mathText
),
textDirection:
config
.
textDirection
,
style:
textStyle
.
copyWith
(
color:
(!
kDebugMode
)
?
null
:
Theme
.
of
(
context
).
colorScheme
.
error
),
);
},
),
onErrorFallback:
(
err
)
{
return
Text
(
workaround
(
mathText
),
textDirection:
config
.
textDirection
,
style:
textStyle
.
copyWith
(
color:
(!
kDebugMode
)
?
null
:
Theme
.
of
(
context
).
colorScheme
.
error
),
);
},
);
return
builder
(
context
,
workaround
(
mathText
),
config
.
style
??
const
TextStyle
(),
false
);
...
...
@@ -591,42 +606,45 @@ class LatexMath extends InlineMd {
var
workaround
=
config
.
latexWorkaround
??
(
String
tex
)
=>
tex
;
var
builder
=
config
.
latexBuilder
??
(
BuildContext
context
,
String
tex
,
TextStyle
textStyle
,
bool
inline
)
=>
Math
.
tex
(
tex
,
textStyle:
textStyle
,
mathStyle:
MathStyle
.
display
,
textScaleFactor:
1
,
settings:
const
TexParserSettings
(
strict:
Strict
.
ignore
,
),
options:
MathOptions
(
sizeUnderTextStyle:
MathSize
.
large
,
color:
config
.
style
?.
color
??
Theme
.
of
(
context
).
colorScheme
.
onSurface
,
fontSize:
config
.
style
?.
fontSize
??
Theme
.
of
(
context
).
textTheme
.
bodyMedium
?.
fontSize
,
mathFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
SelectableAdapter
(
selectedText:
tex
,
child:
Math
.
tex
(
tex
,
textStyle:
textStyle
,
mathStyle:
MathStyle
.
display
,
textScaleFactor:
1
,
settings:
const
TexParserSettings
(
strict:
Strict
.
ignore
,
),
textFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
options:
MathOptions
(
sizeUnderTextStyle:
MathSize
.
large
,
color:
config
.
style
?.
color
??
Theme
.
of
(
context
).
colorScheme
.
onSurface
,
fontSize:
config
.
style
?.
fontSize
??
Theme
.
of
(
context
).
textTheme
.
bodyMedium
?.
fontSize
,
mathFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
),
textFontOptions:
FontOptions
(
fontFamily:
"Main"
,
fontWeight:
config
.
style
?.
fontWeight
??
FontWeight
.
normal
,
fontShape:
FontStyle
.
normal
,
),
style:
MathStyle
.
display
,
),
style:
MathStyle
.
display
,
onErrorFallback:
(
err
)
{
return
Text
(
workaround
(
mathText
),
textDirection:
config
.
textDirection
,
style:
textStyle
.
copyWith
(
color:
(!
kDebugMode
)
?
null
:
Theme
.
of
(
context
).
colorScheme
.
error
),
);
},
),
onErrorFallback:
(
err
)
{
return
Text
(
workaround
(
mathText
),
textDirection:
config
.
textDirection
,
style:
textStyle
.
copyWith
(
color:
(!
kDebugMode
)
?
null
:
Theme
.
of
(
context
).
colorScheme
.
error
),
);
},
);
return
WidgetSpan
(
alignment:
PlaceholderAlignment
.
baseline
,
...
...
@@ -655,7 +673,6 @@ class SourceTag extends InlineMd {
}
return
WidgetSpan
(
alignment:
PlaceholderAlignment
.
middle
,
// baseline: TextBaseline.alphabetic,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
2
),
child:
config
.
sourceTagBuilder
...
...
@@ -696,8 +713,11 @@ class ATagMd extends InlineMd {
if
(
match
?[
1
]
==
null
&&
match
?[
2
]
==
null
)
{
return
const
TextSpan
();
}
var
theme
=
GptMarkdownTheme
.
of
(
context
);
return
WidgetSpan
(
child:
LinkButton
(
hoverColor:
theme
.
linkHoverColor
,
color:
theme
.
linkColor
,
onPressed:
()
{
config
.
onLinkTab
?.
call
(
"
${match?[2]}
"
,
"
${match?[1]}
"
);
},
...
...
@@ -762,6 +782,9 @@ class ImageMd extends InlineMd {
/// Table component
class
TableMd
extends
BlockMd
{
@override
String
get
expString
=>
(
r"(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$"
);
@override
Widget
build
(
BuildContext
context
,
String
text
,
...
...
@@ -847,28 +870,19 @@ class TableMd extends BlockMd {
),
);
}
@override
RegExp
get
exp
=>
RegExp
(
r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$"
,
);
}
class
CodeBlockMd
extends
BlockMd
{
@override
RegExp
get
exp
=>
RegExp
(
r"\s*?```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$"
,
multiLine:
true
,
dotAll:
true
,
);
String
get
expString
=>
r"```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$"
;
@override
Widget
build
(
BuildContext
context
,
String
text
,
final
GptMarkdownConfig
config
,
)
{
String
codes
=
exp
.
firstMatch
(
text
)?[
2
]
??
""
;
String
name
=
exp
.
firstMatch
(
text
)?[
1
]
??
""
;
String
codes
=
this
.
exp
.
firstMatch
(
text
)?[
2
]
??
""
;
String
name
=
this
.
exp
.
firstMatch
(
text
)?[
1
]
??
""
;
codes
=
codes
.
replaceAll
(
r"```"
,
""
).
trim
();
return
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
...
...
@@ -878,75 +892,3 @@ class CodeBlockMd extends BlockMd {
);
}
}
class
CodeField
extends
StatefulWidget
{
const
CodeField
({
super
.
key
,
required
this
.
name
,
required
this
.
codes
});
final
String
name
;
final
String
codes
;
@override
State
<
CodeField
>
createState
()
=>
_CodeFieldState
();
}
class
_CodeFieldState
extends
State
<
CodeField
>
{
bool
_copied
=
false
;
@override
Widget
build
(
BuildContext
context
)
{
return
Material
(
color:
Theme
.
of
(
context
).
colorScheme
.
onInverseSurface
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
[
Row
(
children:
[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
,
vertical:
8
),
child:
Text
(
widget
.
name
),
),
const
Spacer
(),
TextButton
.
icon
(
style:
TextButton
.
styleFrom
(
foregroundColor:
Theme
.
of
(
context
).
colorScheme
.
onSurface
,
textStyle:
const
TextStyle
(
fontWeight:
FontWeight
.
normal
,
),
),
onPressed:
()
async
{
await
Clipboard
.
setData
(
ClipboardData
(
text:
widget
.
codes
))
.
then
((
value
)
{
setState
(()
{
_copied
=
true
;
});
});
await
Future
.
delayed
(
const
Duration
(
seconds:
2
));
setState
(()
{
_copied
=
false
;
});
},
icon:
Icon
(
(
_copied
)
?
Icons
.
done
:
Icons
.
content_paste
,
size:
15
,
),
label:
Text
((
_copied
)
?
"Copied!"
:
"Copy code"
),
),
],
),
const
Divider
(
height:
1
,
),
SingleChildScrollView
(
scrollDirection:
Axis
.
horizontal
,
padding:
const
EdgeInsets
.
all
(
16
),
child:
Text
(
widget
.
codes
,
),
),
],
),
);
}
}
...
...
lib/md_widget.dart
View file @
11ce4c5
...
...
@@ -16,16 +16,17 @@ class MdWidget extends StatelessWidget {
list
.
addAll
(
MarkdownComponent
.
generate
(
context
,
exp
.
replaceAllMapped
(
RegExp
(
r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})"
,
multiLine:
true
,
dotAll:
true
,
),
(
match
)
{
//
String
body
=
(
match
[
1
]
??
match
[
2
])?.
replaceAll
(
"
\n
"
,
" "
)
??
""
;
return
"
\\
[
$body
\\
]"
;
}),
exp
,
// .replaceAllMapped(
// RegExp(
// r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})",
// multiLine: true,
// dotAll: true,
// ), (match) {
// //
// String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? "";
// return "\\[$body\\]";
// }),
config
,
),
);
...
...
lib/theme.dart
View file @
11ce4c5
part of
'gpt_markdown.dart'
;
/// Theme defined for `
Tex
Markdown` widget
/// Theme defined for `
Gpt
Markdown` widget
class
GptMarkdownThemeData
extends
ThemeExtension
<
GptMarkdownThemeData
>
{
GptMarkdownThemeData
({
GptMarkdownThemeData
.
_
({
required
this
.
highlightColor
,
required
this
.
h1
,
required
this
.
h2
,
required
this
.
h3
,
required
this
.
h4
,
required
this
.
h5
,
required
this
.
h6
,
required
this
.
hrLineThickness
,
required
this
.
hrLineColor
,
required
this
.
linkColor
,
required
this
.
linkHoverColor
,
});
/// Define default attributes.
factory
GptMarkdownThemeData
.
from
(
BuildContext
context
)
{
return
GptMarkdownThemeData
(
highlightColor:
Theme
.
of
(
context
).
colorScheme
.
onSurfaceVariant
.
withAlpha
(
50
),
factory
GptMarkdownThemeData
({
required
Brightness
brightness
,
Color
?
highlightColor
,
TextStyle
?
h1
,
TextStyle
?
h2
,
TextStyle
?
h3
,
TextStyle
?
h4
,
TextStyle
?
h5
,
TextStyle
?
h6
,
double
?
hrLineThickness
,
Color
?
hrLineColor
,
Color
?
linkColor
,
Color
?
linkHoverColor
,
})
{
ThemeData
themeData
=
switch
(
brightness
)
{
Brightness
.
light
=>
ThemeData
.
light
(),
Brightness
.
dark
=>
ThemeData
.
dark
(),
};
final
typography
=
Typography
.
tall2021
.
copyWith
(
displayLarge:
Typography
.
tall2021
.
displayLarge
?.
copyWith
(
inherit:
true
),
displayMedium:
Typography
.
tall2021
.
displayMedium
?.
copyWith
(
inherit:
true
),
displaySmall:
Typography
.
tall2021
.
displaySmall
?.
copyWith
(
inherit:
true
),
headlineLarge:
Typography
.
tall2021
.
headlineLarge
?.
copyWith
(
inherit:
true
),
headlineMedium:
Typography
.
tall2021
.
headlineMedium
?.
copyWith
(
inherit:
true
),
headlineSmall:
Typography
.
tall2021
.
headlineSmall
?.
copyWith
(
inherit:
true
),
titleLarge:
Typography
.
tall2021
.
titleLarge
?.
copyWith
(
inherit:
true
),
titleMedium:
Typography
.
tall2021
.
titleMedium
?.
copyWith
(
inherit:
true
),
titleSmall:
Typography
.
tall2021
.
titleSmall
?.
copyWith
(
inherit:
true
),
bodyLarge:
Typography
.
tall2021
.
bodyLarge
?.
copyWith
(
inherit:
true
),
bodyMedium:
Typography
.
tall2021
.
bodyMedium
?.
copyWith
(
inherit:
true
),
bodySmall:
Typography
.
tall2021
.
bodySmall
?.
copyWith
(
inherit:
true
),
labelLarge:
Typography
.
tall2021
.
labelLarge
?.
copyWith
(
inherit:
true
),
labelMedium:
Typography
.
tall2021
.
labelMedium
?.
copyWith
(
inherit:
true
),
labelSmall:
Typography
.
tall2021
.
labelSmall
?.
copyWith
(
inherit:
true
),
);
themeData
=
themeData
.
copyWith
(
textTheme:
typography
,
);
TextTheme
textTheme
=
themeData
.
textTheme
;
return
GptMarkdownThemeData
.
_fromTheme
(
themeData
,
textTheme
).
copyWith
(
highlightColor:
highlightColor
,
h1:
h1
,
h2:
h2
,
h3:
h3
,
h4:
h4
,
h5:
h5
,
h6:
h6
,
hrLineThickness:
hrLineThickness
,
hrLineColor:
hrLineColor
,
linkColor:
linkColor
,
linkHoverColor:
linkHoverColor
,
);
}
factory
GptMarkdownThemeData
.
_fromTheme
(
ThemeData
theme
,
TextTheme
textTheme
)
{
return
GptMarkdownThemeData
.
_
(
highlightColor:
theme
.
colorScheme
.
onSurfaceVariant
.
withAlpha
(
50
),
h1:
textTheme
.
headlineLarge
,
h2:
textTheme
.
headlineMedium
,
h3:
textTheme
.
headlineSmall
,
h4:
textTheme
.
titleLarge
,
h5:
textTheme
.
titleMedium
,
h6:
textTheme
.
titleSmall
,
hrLineThickness:
1
,
hrLineColor:
theme
.
colorScheme
.
outline
,
linkColor:
Colors
.
blue
,
linkHoverColor:
Colors
.
red
,
);
}
Color
highlightColor
;
TextStyle
?
h1
;
TextStyle
?
h2
;
TextStyle
?
h3
;
TextStyle
?
h4
;
TextStyle
?
h5
;
TextStyle
?
h6
;
double
hrLineThickness
;
Color
hrLineColor
;
Color
linkColor
;
Color
linkHoverColor
;
/// Define default attributes.
@override
GptMarkdownThemeData
copyWith
({
Color
?
highlightColor
})
{
return
GptMarkdownThemeData
(
GptMarkdownThemeData
copyWith
({
Color
?
highlightColor
,
TextStyle
?
h1
,
TextStyle
?
h2
,
TextStyle
?
h3
,
TextStyle
?
h4
,
TextStyle
?
h5
,
TextStyle
?
h6
,
double
?
hrLineThickness
,
Color
?
hrLineColor
,
Color
?
linkColor
,
Color
?
linkHoverColor
,
})
{
return
GptMarkdownThemeData
.
_
(
highlightColor:
highlightColor
??
this
.
highlightColor
,
h1:
h1
??
this
.
h1
,
h2:
h2
??
this
.
h2
,
h3:
h3
??
this
.
h3
,
h4:
h4
??
this
.
h4
,
h5:
h5
??
this
.
h5
,
h6:
h6
??
this
.
h6
,
hrLineThickness:
hrLineThickness
??
this
.
hrLineThickness
,
hrLineColor:
hrLineColor
??
this
.
hrLineColor
,
linkColor:
linkColor
??
this
.
linkColor
,
linkHoverColor:
linkHoverColor
??
this
.
linkHoverColor
,
);
}
...
...
@@ -28,9 +135,21 @@ class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> {
if
(
other
==
null
)
{
return
this
;
}
return
GptMarkdownThemeData
(
return
GptMarkdownThemeData
.
_
(
highlightColor:
Color
.
lerp
(
highlightColor
,
other
.
highlightColor
,
t
)
??
highlightColor
,
h1:
TextStyle
.
lerp
(
h1
,
other
.
h1
,
t
)
??
h1
,
h2:
TextStyle
.
lerp
(
h2
,
other
.
h2
,
t
)
??
h2
,
h3:
TextStyle
.
lerp
(
h3
,
other
.
h3
,
t
)
??
h3
,
h4:
TextStyle
.
lerp
(
h4
,
other
.
h4
,
t
)
??
h4
,
h5:
TextStyle
.
lerp
(
h5
,
other
.
h5
,
t
)
??
h5
,
h6:
TextStyle
.
lerp
(
h6
,
other
.
h6
,
t
)
??
h6
,
hrLineThickness:
Tween
(
begin:
hrLineThickness
,
end:
other
.
hrLineThickness
)
.
transform
(
t
),
hrLineColor:
Color
.
lerp
(
hrLineColor
,
other
.
hrLineColor
,
t
)
??
hrLineColor
,
linkColor:
Color
.
lerp
(
linkColor
,
other
.
linkColor
,
t
)
??
linkColor
,
linkHoverColor:
Color
.
lerp
(
linkHoverColor
,
other
.
linkHoverColor
,
t
)
??
linkHoverColor
,
);
}
}
...
...
@@ -45,16 +164,17 @@ class GptMarkdownTheme extends InheritedWidget {
final
GptMarkdownThemeData
gptThemeData
;
static
GptMarkdownThemeData
of
(
BuildContext
context
)
{
var
theme
=
Theme
.
of
(
context
);
final
provider
=
context
.
dependOnInheritedWidgetOfExactType
<
GptMarkdownTheme
>();
if
(
provider
!=
null
)
{
return
provider
.
gptThemeData
;
}
final
themeData
=
Theme
.
of
(
context
)
.
extension
<
GptMarkdownThemeData
>();
final
themeData
=
theme
.
extension
<
GptMarkdownThemeData
>();
if
(
themeData
!=
null
)
{
return
themeData
;
}
return
GptMarkdownThemeData
.
from
(
context
);
return
GptMarkdownThemeData
.
_fromTheme
(
theme
,
theme
.
textTheme
);
}
@override
...
...
pubspec.yaml
View file @
11ce4c5
name
:
gpt_markdown
description
:
"
The
purpose
of
this
package
is
to
render
the
response
of
ChatGPT
into
a
Flutter
app."
version
:
0.1.14
description
:
"
Powerful
Markdown
&
LaTeX
Renderer
for
Flutter:
Rich
Text,
Math,
Tables,
Links,
and
Text
Selection.
Ideal
for
ChatGPT,
Gemini,
and
more."
version
:
1.0.0
homepage
:
https://github.com/Infinitix-LLC/gpt_markdown
environment
:
...
...
@@ -10,46 +10,21 @@ environment:
dependencies
:
flutter
:
sdk
:
flutter
flutter_math_fork
:
^0.7.
2
flutter_math_fork
:
^0.7.
3
dev_dependencies
:
flutter_test
:
sdk
:
flutter
flutter_lints
:
^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter
:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages
topics
:
-
markdown
-
latex
-
selectable
-
math
-
chatgpt
-
gemini
-
ai
-
gpt
...
...
Please
register
or
login
to post a comment