Skip to content

Instantly share code, notes, and snippets.

@ltvu93
Last active January 30, 2024 03:26
Show Gist options
  • Save ltvu93/9df3536c4eee22dc8bc8b7ffea614f7f to your computer and use it in GitHub Desktop.
Save ltvu93/9df3536c4eee22dc8bc8b7ffea614f7f to your computer and use it in GitHub Desktop.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:html/dom.dart' as dom;
void main() {
runApp(TestApp());
}
class TestApp extends StatelessWidget {
const TestApp({super.key});
@override
Widget build(BuildContext context) {
final htmlText = '''
<h1>Free Online HTML Editor</h1>
<p>Feel free to use this <strong>free online HTML editor</strong> to generate HTML code for your own website, blog, email newsletter, etc.</p>
<p>To get started:</p>
<ul>
<li>Delete this content and enter your own content</li>
<li>To view the source code, simply select the <kbd>Tools &gt; Source</kbd> menu option above</li>
<li>Rinse and repeat as required</li>
<li>Copy and paste the source code into your own website, blog, email newsletter, etc</li>
</ul>
<p>Go ahead - try it!</p>
<p>HTML code generated at <a href="https://htmleditor.online/">htmleditor.online</a>.</p>
''';
return MaterialApp(
home: Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: ReadMoreHtml(
htmlText: htmlText,
),
),
),
),
);
}
}
class ReadMoreHtml extends StatefulWidget {
final String htmlText;
final TextStyle? style;
final int maxTextLength;
final String showMoreText;
final TextStyle? showMoreStyle;
final VoidCallback? onShowMorePressed;
const ReadMoreHtml({
Key? key,
required this.htmlText,
this.style,
this.maxTextLength = 120,
this.showMoreText = 'read more',
this.showMoreStyle,
this.onShowMorePressed,
}) : super(key: key);
@override
State<ReadMoreHtml> createState() => _ReadMoreHtmlState();
}
class _ReadMoreHtmlState extends State<ReadMoreHtml> {
late dom.Element fullHtmlElement;
late dom.Element cutHtmlElement;
bool expanded = false;
static const String _kEllipsis = '\u2026';
static const String _showMoreTag = 'showMore';
@override
void initState() {
super.initState();
_processData();
}
@override
void didUpdateWidget(oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.htmlText != oldWidget.htmlText || widget.maxTextLength != oldWidget.maxTextLength) {
_processData();
}
}
void _processData() {
fullHtmlElement = HtmlParser.parseHTML(widget.htmlText);
cutHtmlElement = fullHtmlElement.clone(true);
if (fullHtmlElement.text.length <= widget.maxTextLength) {
return;
}
curTextCount = 0;
cutElementText(cutHtmlElement, widget.maxTextLength);
addReadMoreElement(cutHtmlElement);
}
int curTextCount = 0;
void cutElementText(dom.Node curNode, int maxTextLength) {
final nodes = [...curNode.nodes];
for (final node in nodes) {
if (node.nodeType == dom.Node.ELEMENT_NODE) {
if (curTextCount >= maxTextLength) {
node.remove();
} else {
cutElementText(node, maxTextLength);
}
}
if (node.nodeType == dom.Node.TEXT_NODE) {
final newTextCount = curTextCount + node.text!.length;
if (newTextCount > maxTextLength) {
node.text = node.text!.substring(0, node.text!.length - (newTextCount - maxTextLength));
}
curTextCount += node.text!.length;
}
}
}
void addReadMoreElement(dom.Element element) {
final lastNode = getLastNode(element);
final readMoreTagElement = dom.Element.tag(_showMoreTag);
final addNode = lastNode.nodeType == dom.Node.ELEMENT_NODE ? lastNode : lastNode.parent;
addNode!.append(readMoreTagElement);
}
dom.Node getLastNode(dom.Node node) {
if (node.nodes.isEmpty) return node;
return getLastNode(node.nodes.last);
}
@override
Widget build(BuildContext context) {
final textStyle = widget.style ?? Theme.of(context).textTheme.bodyMedium!;
return Html.fromElement(
documentElement: expanded ? fullHtmlElement : cutHtmlElement,
style: {
'html': Style.fromTextStyle(textStyle),
'body': Style(
margin: Margins.zero,
padding: HtmlPaddings.zero,
),
'p': Style(
margin: Margins.zero,
padding: HtmlPaddings.zero,
),
},
extensions: [
TagExtension(
tagsToExtend: {_showMoreTag},
builder: (extensionContext) {
return RichText(
text: TextSpan(
style: textStyle,
children: [
TextSpan(
text: '$_kEllipsis ',
),
if (widget.showMoreText.isNotEmpty) ...[
TextSpan(
text: widget.showMoreText,
style: widget.showMoreStyle ?? textStyle.copyWith(color: Colors.pink),
recognizer: TapGestureRecognizer()..onTap = onReadMorePressed,
),
],
],
),
);
},
),
],
);
}
void onReadMorePressed() {
setState(() {
expanded = true;
});
widget.onShowMorePressed?.call();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment