Skip to main content

livemd_flutter 0.4.1 copy "livemd_flutter: ^0.4.1" to clipboard
livemd_flutter: ^0.4.1 copied to clipboard

A lossless continuous Markdown Live Preview editor for Flutter.

LiveMD Flutter #

A lossless, continuous Markdown Live Preview editor for Flutter. It follows the editing model used by Obsidian rather than a conventional WYSIWYG editor: Markdown remains the only source of truth, rendered syntax stays in the same native editing surface, and the caret reveals source only when it enters that source range.

Quick start (5 minutes) #

Requires Dart ^3.13.1 and Flutter >=3.35.0. Your Flutter installation must satisfy both constraints; check with flutter --version.

1. Install #

From your Flutter app's directory, run:

flutter pub add livemd_flutter:^0.4.1

2. Replace lib/main.dart #

This complete example runs without assets, storage services, or player plugins:

import 'package:flutter/material.dart';
import 'package:livemd_flutter/livemd_flutter.dart';

void main() => runApp(const MaterialApp(home: MarkdownPage()));

class MarkdownPage extends StatefulWidget {
  const MarkdownPage({super.key});

  @override
  State<MarkdownPage> createState() => _MarkdownPageState();
}

class _MarkdownPageState extends State<MarkdownPage> {
  late final LiveMarkdownController _controller;

  @override
  void initState() {
    super.initState();
    _controller = LiveMarkdownController(
      text: '# Hello\n\nEdit **Markdown** here.\n\n- [ ] Try a task',
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Markdown editor')),
      body: LiveMarkdownEditor(
        controller: _controller,
        onChanged: (markdown) {
          // Demo only: replace this with your app's persistence logic.
          debugPrint(markdown);
        },
      ),
    );
  }
}

3. Run #

flutter run

You should see an editable heading, bold text, and a task checkbox. Move the caret into formatted text to reveal its Markdown source. Changes are printed to the debug console, not saved; restarting the app restores the sample.

Already have an app? Use MarkdownPage as a route, or put LiveMarkdownEditor in your own page. Create the controller once in initState, not in build, and dispose it when the page is removed. Inside a Column, wrap the editor in Expanded to give it a bounded editing area.

Connect it to your app #

  • Load and save: pass your stored Markdown as the controller's initial text. Read controller.text for the current source, or persist the markdown received by onChanged. The package does not provide file, database, or cloud storage. Debounce frequent writes in your app if needed.
  • Open links: implement onLinkTap: (destination, label) { ... } for external links and onWikiLinkTap: (target, alias) { ... } for note navigation. The editor reports taps; your app validates destinations and decides where to navigate.
  • Task checkboxes: toggling a task updates the Markdown automatically. Use onTaskToggle only if you also need a task-specific notification.
  • Images and playback: network and asset: images have a default resolver; declare asset files in your app's pubspec.yaml. Use imageResolver for custom image loading, and inject videoBuilder / audioBuilder for actual playback. No audio/video player is bundled.

For parameters and types, see the API reference. The quick-start source matches the example above. The full example app demonstrates links, tables, images, and injected audio/video players. To run either from a checkout of this repository:

cd example
flutter pub get
flutter run -t lib/quick_start.dart # Minimal editor
# Or: flutter run                 # Full feature demo

Why the continuous engine matters #

The editor owns one global TextEditingValue, selection, composing range, and undo history for the entire document. Its main surface is one continuous native editor projected from exact UTF-16 source ranges. A pointer-edited table cell temporarily receives native text focus, but every local value is mapped back into that same global source and history.

That makes these native editing behaviors possible:

  • selection, Shift+Arrow, copy, and IME composition across block boundaries;
  • programmatic global selections that stay synchronized with the visible caret;
  • one undo/redo history for typing, structural commands, and task toggles;
  • stable line boxes and baselines while inline source markers appear;
  • pointer actions that are distinct from arrow-key source traversal.

For example, clicking a rendered link invokes onLinkTap, while moving the caret into that same link with an arrow key reveals [label](destination). Clicking a task checkbox toggles [ ]/[x]; moving left from the task text into the prefix reveals the complete task source.

Supported behavior #

Area Current support
Document editing One global source, selection, composing range, scroll surface, and undo/redo history
Headings ATX levels 1–6, closing hashes, stable content typography and baseline
Inline Nested bold/italic, strikethrough, highlight, multi-backtick code spans
Links Markdown destinations/titles, balanced destination parentheses, Wiki targets and aliases, pointer callbacks
Images Standalone previews (network or asset: destinations) and line-height inline thumbnails via an injectable resolver; pointer clicks report onImageTap, while source reveal keeps a standalone preview below the source line
Video ![alt](destination.mp4) uses an injectable videoBuilder: a 240 px standalone preview or a line-height inline projection, with distinct pointer action and source traversal
Audio ![alt](destination.mp3) uses an injectable audioBuilder: a compact 48 px standalone player or a line-height inline projection, with distinct pointer action and source traversal
Literal states Escapes, word-internal underscores, and incomplete syntax stay lossless and literal
Lists Unordered, ordered, tasks, nesting, Enter continuation/exit, Shift+Enter continuation, Tab/Shift+Tab, marker Backspace
Ordered lists Following same-level items are renumbered after structural Enter
Quotes Rendered quote marker plus Enter/exit/nesting commands
Fenced code The full-width code container stays mounted while fences/source appear; raw and rendered text share one width, scaler, and height budget
Tables One real GFM table layout drives rich inline/media rendering, hit testing, wrapped row heights, and native pointer cell editing; arrow traversal opens a native whole-table source layer in the same footprint; Tab/Shift+Tab/Enter navigate cells, gutter controls add rows/columns, and the context menu mutates both axes
Other complex blocks Pointer-edit Callout titles and bodies inside the rendered shell; keyboard source traversal and thematic breaks preserve the block height budget
Layout No preview/editor remount; rendered Markdown owns its run-metric line heights (heading lines, rows, inline placeholders) with regression tests for Y stability

Without a real wrap, inline media reveal at the same footprint as their source, so following text keeps its X/Y position. Standalone media are the deliberate exception: revealing source adds exactly one normal text line above the retained preview, while the preview itself neither resizes nor collapses. Media source uses a natural-height caret rather than stretching it to the preview. If an embed source cannot fit on one line at the available width, the editor keeps it as literal source instead of constructing a projection that would overflow or reorder its wrapped lines.

The positioned syntax tree is intentionally conservative while text is being edited: unsupported or incomplete constructs remain literal source instead of being guessed and reformatted.

Advanced source access #

LiveMarkdownController.value exposes the complete TextEditingValue; the compatibility properties text and selection remain available. The public MarkdownSyntaxParser also exposes lossless UTF-16 SourceRange nodes for applications that need source-aware commands or decorations.

Table structure operations are also available through applyMarkdownEditingCommand: insert/delete the current row or column with the six tableInsert... / tableDelete... enum values. The built-in table hover controls and context menu use the same command path, so each mutation is one document undo transaction.

Using as a dependency #

The library is a pure Dart/Flutter package with no native code, so it runs on every Flutter platform. Add it to your pubspec.yaml:

dependencies:
  livemd_flutter: ^0.4.1

The package requires Dart ^3.13.1 and Flutter >=3.35.0. Alternatively, pin the Git release tag:

dependencies:
  livemd_flutter:
    git:
      url: https://github.com/sd0116baka/livemd-flutter.git
      ref: v0.4.1

Media playback is intentionally app-owned: the editor renders standalone media slots and compact inline projections, and reports pointer actions, but the actual player widget is injected through imageResolver, videoBuilder, and audioBuilder. The example app wires video_player and just_audio; your app can supply any player. Revealing a standalone media source intentionally adds one source line above the still-visible preview, matching the observed Obsidian media-source treatment instead of collapsing the preview.

Current boundary #

Callout titles and bodies support pointer editing inside the rendered shell, while keyboard traversal can reveal the raw block source. A table pointer click uses a rendered cell editor, while arrow-key traversal opens a native whole-table source editor over an invisible copy of the same table geometry, so the following content stays fixed even for wrapped rich cells. Both the active cell and whole-table editor map selection, composing ranges, and undo transactions back to global UTF-16 offsets. A standalone media preview remains visible while its source is active, but its embedded controls are temporarily non-interactive; leave the source range to operate the player again. Obsidian ![[...]] vault embeds/transclusions, math, footnotes, reference links, and full CommonMark delimiter-flanking coverage are also future work.

The repository includes the observed behavior oracle in doc/obsidian_live_preview_behavior.md and a broad sample document in test/fixtures/obsidian_live_preview_matrix.md.

0
likes
0
points
235
downloads

Publisher

unverified uploader

Weekly Downloads

A lossless continuous Markdown Live Preview editor for Flutter.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_markdown_plus

More

Packages that depend on livemd_flutter