Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
},
"type": "shell",
"command": [
"dart format --fix -l 80 lib test"
"dart format -l 80 lib test"
],
"dependsOn": [],
"args": [],
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.0.7

- **FIXED**: Preserved indentation on line breaks within list items [#4].
- **FIXED**: Inline code no longer processes inner Markdown syntax [#10].
- **CHANGED**: Improved theme support.
- **ADDED**: Dark mode support in the example app.

## 0.0.6

- **FIXED**: Fixed escaping of special characters. [#6]
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ MarkdownTheme(
)
```

Or you can use the `MarkdownThemeData.mergeTheme(Theme.of(context))` factory to create a theme that inherits from the application's theme.
This approach allows you to easily support both light and dark themes, and keeps your markdown styling consistent with the rest of your application.

### Custom Block Painters

For advanced customization, you can provide custom block painters:
Expand Down
69 changes: 55 additions & 14 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_md/flutter_md.dart';

void main() => runZonedGuarded<void>(
() => runApp(const App()),
() => runApp(ThemeModel(
notifier: ValueNotifier<ThemeMode>(ThemeMode.dark),
child: const App())),
(e, s) => print(e),
);

Expand All @@ -17,21 +19,16 @@ class App extends StatelessWidget {
/// {@macro app}
const App({super.key});

/// Light theme for the app.
static final ThemeData theme = ThemeData.light();

@override
Widget build(BuildContext context) => MaterialApp(
title: 'Markdown',
themeMode: ThemeMode.light,
theme: theme,
darkTheme: theme,
themeMode: ThemeModel.of(context).value,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const HomeScreen(),
builder: (context, child) => MarkdownTheme(
data: MarkdownThemeData(
textStyle: const TextStyle(fontSize: 14.0, color: Colors.black),
textDirection: TextDirection.ltr,
textScaler: TextScaler.noScaling,
data: MarkdownThemeData.mergeTheme(
Theme.of(context),
// Exclude images from the markdown rendering,
// so they are not rendered in the output.
// Because image spans are not supported yet.
Expand All @@ -52,6 +49,42 @@ class App extends StatelessWidget {
);
}

/// {@template theme_model}
/// A widget that provides the [ThemeMode] to its descendants.
/// {@endtemplate}
class ThemeModel extends InheritedNotifier<ValueNotifier<ThemeMode>> {
/// {@macro theme_model}
const ThemeModel({super.key, super.notifier, required super.child});

/// The state from the closest instance of this class
/// that encloses the given context, if any.
/// e.g. `Theme.maybeOf(context)`.
static ValueNotifier<ThemeMode>? maybeOf(BuildContext context,
{bool listen = true}) =>
listen
? context.dependOnInheritedWidgetOfExactType<ThemeModel>()?.notifier
: context.getInheritedWidgetOfExactType<ThemeModel>()?.notifier;

static Never _notFoundInheritedWidgetOfExactType() => throw ArgumentError(
'Out of scope, not found inherited widget '
'a ThemeModel of the exact type',
'out_of_scope',
);

/// The state from the closest instance of this class
/// that encloses the given context.
/// e.g. `Theme.of(context)`
static ValueNotifier<ThemeMode> of(BuildContext context,
{bool listen = true}) =>
maybeOf(context, listen: listen) ?? _notFoundInheritedWidgetOfExactType();

@override
bool updateShouldNotify(
covariant InheritedNotifier<ValueNotifier<ThemeMode>> oldWidget) {
return !identical(notifier, oldWidget.notifier);
}
}

/// {@template home_screen}
/// HomeScreen widget.
/// {@endtemplate}
Expand Down Expand Up @@ -104,9 +137,17 @@ class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Markdown'),
),
centerTitle: true,
title: const Text('Markdown'),
actions: <Widget>[
// theme switch widget
Switch.adaptive(
value: ThemeModel.of(context).value == ThemeMode.dark,
onChanged: (value) {
ThemeModel.of(context).value =
value ? ThemeMode.dark : ThemeMode.light;
}),
]),
body: SafeArea(
child: CustomMultiChildLayout(
delegate: _layoutDelegate,
Expand Down
33 changes: 24 additions & 9 deletions lib/src/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -453,17 +453,19 @@ List<MD$Span> _parseInlineSpans(String text) {

for (var i = 0; i < length; i++) {
final ch = codes[i];
// Check for escaped characters
if (ch == esc /* \ */ && i != length - 1) {
final nextChar = codes[i + 1];
// Check if the next character is an escaped character
if (_escapedChars.length > nextChar && _escapedChars[nextChar] == 1) {
hasExcluded = true; // We have an escaped character
excluded.add(i); // Exclude this character as it is escaped
i++; // skip next char

continue;
// If we are inside a monospace block, we should only look
// for the closing backtick.
if (mask.contains(MD$Style.monospace)) {
if (ch == 96 /* ` */ && i > 0 && codes[i - 1] != esc /* ignore \` */) {
// Found closing backtick
maybePushSpan(i);
mask ^= MD$Style.monospace;
start = i + 1;
}
// We continue to the next character, ignoring any other
// special markers.
continue;
}

// If this character is part of a link or image, skip it
Expand All @@ -478,6 +480,19 @@ List<MD$Span> _parseInlineSpans(String text) {
continue;
}

// Check for escaped characters
if (ch == esc /* \ */ && i != length - 1) {
final nextChar = codes[i + 1];
// Check if the next character is an escaped character
if (_escapedChars.length > nextChar && _escapedChars[nextChar] == 1) {
hasExcluded = true; // We have an escaped character
excluded.add(i); // Exclude this character as it is escaped
i++; // skip next char

continue;
}
}

// If the character is not a special inline marker, continue
if (_kind.length > ch && _kind[ch] == 0) continue;

Expand Down
Loading