Skip to content

Commit

Permalink
chore(PostView): 使用 MarkdownBody 显示 Markdown 文本
Browse files Browse the repository at this point in the history
移除 markdown_widget, flutter_inappwebview 改为使用 flutter_markdown, flutter_markdown_latex 显示预览
  • Loading branch information
wonder-light committed Jan 12, 2025
1 parent e9b4793 commit 0a83640
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 405 deletions.
2 changes: 1 addition & 1 deletion docs/zh-cn/docs/guide/ability.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

- 文章
- [ ] 根据选择的主题来渲染 markdown, 并将起呈现在 preview,
- [ ] 需要优化 preview
- [x] 需要优化 preview
- [ ] 可以根据主题提供的配置类自定义渲染 markdown
- 渲染
- [x] 使用 Jinja2 来渲染模板文件
17 changes: 7 additions & 10 deletions lib/components/Common/drawer_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,17 @@ abstract class DrawerEditorState<T extends DrawerEditor> extends State<T> {

/// 包装字段
Widget wrapperField({required Widget child, String? name}) {
return Container(
margin: kTopPadding8 * 2.25,
child: Column(
final padding = kTopPadding8.flipped;
if (name != null) {
child = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (name != null)
Padding(
padding: kTopPadding8.flipped,
child: Text(name.tr),
),
Padding(padding: padding, child: Text(name.tr)),
child,
],
),
);
);
}
return Container(margin: padding * 2.25, child: child);
}

/// 关闭或者取消
Expand Down
2 changes: 1 addition & 1 deletion lib/components/post/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class PostContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(Get.context!);
final style = theme.textTheme.bodyMedium;
final style = theme.textTheme.bodyMedium?.apply(fontSizeFactor: 1.1);
return CodeEditor(
scrollbarBuilder: buildScrollbar,
controller: controller,
Expand Down
164 changes: 103 additions & 61 deletions lib/components/post/preview.dart
Original file line number Diff line number Diff line change
@@ -1,84 +1,126 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart' show Get, GetNavigationExt, Inst;
import 'package:glidea/controller/site/site.dart';
import 'package:flutter_markdown/flutter_markdown.dart' show MarkdownBody, MarkdownStyleSheet;
import 'package:flutter_markdown_latex/flutter_markdown_latex.dart' show LatexElementBuilder;
import 'package:glidea/components/Common/drawer_editor.dart';
import 'package:glidea/helpers/constants.dart';
import 'package:glidea/helpers/date.dart';
import 'package:glidea/helpers/image.dart';
import 'package:glidea/helpers/markdown.dart';
import 'package:glidea/models/post.dart';
import 'package:markdown_widget/markdown_widget.dart' show MarkdownConfig, MarkdownGenerator, MarkdownWidget;

class PostPreview extends StatefulWidget {
const PostPreview({super.key, required this.entity, required this.markdown});

/// 实体
final Post entity;
/// 预览 [Post]
class PostPreview extends DrawerEditor<Post> {
const PostPreview({
super.key,
required super.entity,
super.controller,
super.header = '',
super.showAction = false,
required this.markdown,
});

/// markdown 内容
final String markdown;

@override
State<PostPreview> createState() => _PostPreviewState();
DrawerEditorState<PostPreview> createState() => _PostPreviewState();
}

class _PostPreviewState extends State<PostPreview> {
/// 站点控制器
final site = Get.find<SiteController>(tag: SiteController.tag);
class _PostPreviewState extends DrawerEditorState<PostPreview> {
/// 时间文本
late final dateText = widget.entity.date.format(pattern: site.themeConfig.dateFormat);

/// 时间文本的样式
late final dateStyle = theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.outline);

/// 定义要为哪些 Markdown 元素使用哪些 TextStyle 对象
late final MarkdownStyleSheet styleSheet = createStyle();

/// 主题颜色
late final theme = Theme.of(Get.context!);
/// 构建控件的函数集合
late final _buildFun = <ValueGetter<Widget>>{
_buildFeature,
_buildTitle,
_buildDate,
if (widget.entity.tags.isNotEmpty) _buildTags,
_buildView,
};

@override
Widget build(BuildContext context) {
// TODO: 改善性能 - 使用 WebView 或者其它方式, 添加 Latex 支持
final colorScheme = theme.colorScheme;
final textTheme = theme.textTheme;
final post = widget.entity;
final dateStr = post.date.format(pattern: site.themeConfig.dateFormat);
final dateStyle = textTheme.bodyMedium?.copyWith(color: colorScheme.outline);
// 控件
final List<Widget> children = [
ImageConfig.builderImg(site.getFeaturePath(widget.entity)),
Text(post.title, style: textTheme.headlineSmall),
Text(dateStr, style: dateStyle),
if (post.tags.isNotEmpty)
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
for (var tag in site.getTagsWithPost(post))
Container(
padding: kVerPadding4 + kHorPadding8,
decoration: BoxDecoration(
color: colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Text(tag.name, style: textTheme.bodySmall),
),
],
),
MarkdownWidget(
data: widget.markdown ?? '',
shrinkWrap: true,
config: MarkdownConfig(configs: [
const ImageConfig(),
]),
markdownGenerator: MarkdownGenerator(
extensionSet: Markdown.custom,
textGenerator: CustomTextNode.new,
),
),
];
// 返回
return Column(
Widget? buildContent(BuildContext context, int index) {
final child = _buildFun.elementAtOrNull(index)?.call();
return child != null ? wrapperField(child: child) : null;
}

/// 封面
Widget _buildFeature() => ImageConfig.builderImg(site.getFeaturePath(widget.entity));

/// 标题
Widget _buildTitle() => Text(widget.entity.title, style: theme.textTheme.headlineMedium);

/// 创建时间
Widget _buildDate() => Text(dateText, style: dateStyle);

/// 构建标签
Widget _buildTags() {
return Row(
spacing: kHorPadding8.right,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
children: [
for (var i = 0; i < children.length; i++)
Padding(
padding: i <= 0 ? kTopPadding8 : kTopPadding16,
child: children[i],
for (var tag in site.getTagsWithPost(widget.entity))
DecoratedBox(
decoration: BoxDecoration(
color: theme.colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(Radius.circular(20)),
),
child: Padding(
padding: kVerPadding4 + kHorPadding8,
child: Text(tag.name, style: theme.textTheme.bodySmall),
),
),
],
);
}

/// 构建 MarkDown 视图
Widget _buildView() {
return MarkdownBody(
selectable: true,
fitContent: false,
data: widget.markdown,
styleSheet: styleSheet,
builders: {
'latex': LatexElementBuilder(
textStyle: theme.textTheme.bodyMedium,
textScaleFactor: 1.2,
),
},
extensionSet: Markdown.custom,
);
}

/// 创建样式
MarkdownStyleSheet createStyle() {
var style = MarkdownStyleSheet.fromTheme(theme);
style = style.copyWith(
pPadding: kVerPadding4,
h1Padding: kVerPadding4,
h2Padding: kVerPadding4,
h3Padding: kVerPadding4,
h4Padding: kVerPadding4,
h5Padding: kVerPadding4,
h6Padding: kVerPadding4,
code: style.code?.copyWith(fontSize: style.p?.fontSize),
codeblockDecoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(2.0),
),
blockquoteDecoration: BoxDecoration(
color: theme.secondaryHeaderColor, //Colors.blue.shade100,
borderRadius: BorderRadius.circular(2.0),
),
textScaler: const TextScaler.linear(1.1),
);
return style;
}
}
2 changes: 1 addition & 1 deletion lib/components/render/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import 'package:glidea/helpers/constants.dart';
import 'package:glidea/helpers/fs.dart';
import 'package:glidea/helpers/get.dart';
import 'package:glidea/helpers/json.dart';
import 'package:glidea/helpers/markdown.dart';
import 'package:glidea/helpers/image.dart';
import 'package:glidea/interfaces/types.dart';
import 'package:glidea/models/render.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart' show PhosphorIconsRegular;
Expand Down
2 changes: 1 addition & 1 deletion lib/helpers/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const tagsTemplate = 'tags.j2';
const tagTemplate = 'tag.j2';

/// 加载 [Post] 中的本地图片的前缀
const String featurePrefix = 'file://';
const String featurePrefix = 'file:///';

/// 摘要分隔符
const String summarySeparator = '<!-- more -->';
Expand Down
32 changes: 31 additions & 1 deletion lib/helpers/image.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'dart:io' show File;

import 'package:flutter/material.dart';
import 'package:glidea/helpers/constants.dart';
import 'package:glidea/helpers/image.dart';
import 'package:image/image.dart' as img show Image, copyResize, encodeJpg, writeFile, decodeImageFile;

/// 扩展 FileImage, 在图片变化时可以进行更新, 包括路径变化、大小变化等
Expand Down Expand Up @@ -45,7 +47,35 @@ extension ImageExt on img.Image {
/// 将 [path] 的图片复制压缩到 [target]
static Future<bool> compress(String path, String target) async {
var image = await img.decodeImageFile(path);
if(image == null) return false;
if (image == null) return false;
return await image.compressImage(target);
}
}

/// config class for image, tag: img
class ImageConfig {
/// 构建图片
static Widget builderImg(String url, {Map<String, String>? attributes, BoxFit fit = BoxFit.cover}) {
if (url.isEmpty) {
return Image.asset('assets/images/upload_image.jpg', errorBuilder: buildError);
}
// 网络图片
if (url.startsWith('http')) {
return Image.network(url, fit: fit, errorBuilder: buildError);
}
// 网络图片
if (url.startsWith('assets')) {
Image.asset(url, fit: fit, errorBuilder: buildError);
}
// post 中的本地图片
if (url.startsWith(featurePrefix)) {
url = url.substring(featurePrefix.length);
}
return Image(image: FileImageExpansion.file(url), fit: fit, errorBuilder: buildError);
}

/// 图片加载失败时的占位图
static Widget buildError(BuildContext context, Object error, StackTrace? stacktrace) {
return Image.asset('assets/images/loading_error.png');
}
}
Loading

0 comments on commit 0a83640

Please sign in to comment.