markdown_widget

repository·main·Indexed 19 days ago

https://github.com/asjqkkkk/markdown_widget

A Markdown rendering component for Flutter that supports Table of Contents (TOC), code highlighting, and extensible support for HTML, LaTeX, and Mermaid diagrams. It provides flexible rendering options via MarkdownWidget, MarkdownGenerator, and MarkdownBlock, with customizable themes for night mode and support for GFM-style tables, task lists, and custom tags.

Tokens
17.6K
Snippets
50
Records
60
Agent score
63%

What's inside markdown_widget

  1. Customize HTML tags with parseHtml and SpanNode

    main

    When using parseHtml to process HTML content, you can define how specific HTML tags are rendered by implementing custom SpanNode classes. If the parser encounters a tag you have registered in your MarkdownGeneratorConfig.generators, it will instantiate your custom SpanNode for that tag.

    For reference, you can implement custom logic for tags like <img> or <video> by following the patterns used in the library's internal img.dart or custom example implementations.

  2. Extend Markdown with Custom Tags

    main

    You can extend the Markdown parser to support custom tags and rendering logic in two ways:

    1. Custom Tags: Pass a SpanNodeGeneratorWithTag to MarkdownGeneratorConfig. This allows you to map new tags to specific SpanNode implementations or override existing ones.
    2. Custom Syntax: Use InlineSyntax and BlockSyntax to define new parsing rules for the Markdown string, which then generate your custom tags.

    For detailed implementation details, refer to the repository's example/lib/markdown_custom directory.

  3. Implement custom tags and nodes

    main

    You can extend the package to support custom tags or override existing ones:

    1. Custom Tags: Pass a SpanNodeGeneratorWithTag to MarkdownGeneratorConfig to add new tags and their corresponding SpanNodes.
    2. Custom Parsing: Use InlineSyntax and BlockSyntax to customize how Markdown strings are parsed and to generate new tags.

    Refer to the custom_node.dart example in the repository for implementation details.

  4. Implement Latex support via custom tags, SpanNodes, and Syntax

    main

    To support mathematical expressions (Latex) wrapped in $$ or $, you must implement three distinct layers in markdown_widget:

    1. Custom Tag: A unique identifier used to link the parsed syntax to a specific rendering logic.
    2. Custom SpanNode: A class extending SpanNode that defines how the parsed content is converted into an InlineSpan (e.g., using a plugin like flutter_math_fork).
    3. Custom Syntax: A class extending m.InlineSyntax that uses regular expressions to identify Latex patterns in the raw text and convert them into elements with the custom tag.

    This pattern allows you to extend the markdown parser to handle any non-standard syntax by intercepting the text during parsing and defining a custom rendering lifecycle.

    const _latexTag = 'latex';
    
    // 1. Define the Tag and Generator
    SpanNodeGeneratorWithTag latexGenerator = SpanNodeGeneratorWithTag(
        tag: _latexTag,  
        generator: (e, config, visitor) =>  
            LatexNode(e.attributes, e.textContent, config));
    
    // 2. Define the Syntax (Regex)
    class LatexSyntax extends m.InlineSyntax {
      LatexSyntax() : super(r'(\$\$[\s\S]+\$\$)|(\$ .+\$)');
      // ... implementation of onMatch
    }
    
    // 3. Define the SpanNode (Rendering)
    class LatexNode extends SpanNode {
      // ... implementation of build()
    }
  5. Implement Custom Tags

    main

    To extend the package with custom tags or override existing ones, you can:

    1. Pass a SpanNodeGeneratorWithTag to MarkdownGeneratorConfig to add new tags and their corresponding SpanNodes.
    2. Customize parsing rules using InlineSyntax and BlockSyntax to generate new tags.

    This allows for features like custom video tags, LaTeX math rendering, or HTML tag extensions.

  6. How Mermaid diagram rendering works

    main

    The Mermaid implementation follows this lifecycle:

    1. Interception: createMermaidWrapper creates a CodeWrapper that intercepts code blocks with language="mermaid".
    2. API Request: The Mermaid code is extracted and sent to the Kroki.io API.
    3. Rendering: The API returns a PNG image of the diagram.
    4. Display States:
      • Loading: Shows a loading indicator (if configured).
      • Ready: Displays the rendered diagram. Diagrams can be clicked to open in a full-screen InteractiveViewer with pan/zoom support.
      • Error: Displays an error message with a retry button. If rendering fails, the system can gracefully fall back to a code-only view.

    Technical Details:

    • Timeout: 15 seconds.
    • Caching: Diagrams are cached in memory using a code|themeName key to prevent redundant API calls.
    • Debounce: A 500ms debounce is applied during editing to prevent excessive API requests.
  7. Create custom SpanNodes for specific HTML tags

    main
    When using parseHtml within a custom ElementNode, you can handle specific HTML tags by implementing corresponding SpanNode classes. This allows you to map specific HTML elements (like <img> or <video>) to custom Flutter widgets. For implementation references, see the project's internal img.dart or custom examples like video.dart.
  8. Configure Night Mode

    main

    You can enable night mode by switching the MarkdownConfig. It is recommended to use MarkdownConfig.darkConfig and update the PreConfig (for code blocks) to match the theme.

    Widget buildMarkdown(BuildContext context) {
      final isDark = Theme.of(context).brightness == Brightness.dark;
      final config = isDark
          ? MarkdownConfig.darkConfig
          : MarkdownConfig.defaultConfig;
      final codeWrapper = (child, text, language) =>
          CodeWrapperWidget(child, text, language);
      return MarkdownWidget(
          data: data,
          config: config.copy(configs: [
          isDark
          ? PreConfig.darkConfig.copy(wrapper: codeWrapper)
          : PreConfig().copy(wrapper: codeWrapper)
      ]));
    }
    Widget buildMarkdown(BuildContext context) {
      final isDark = Theme.of(context).brightness == Brightness.dark;
      final config = isDark
          ? MarkdownConfig.darkConfig
          : MarkdownConfig.defaultConfig;
      final codeWrapper = (child, text, language) =>
          CodeWrapperWidget(child, text, language);
      return MarkdownWidget(
          data: data,
          config: config.copy(configs: [
          isDark
          ? PreConfig.darkConfig.copy(wrapper: codeWrapper)
          : PreConfig().copy(wrapper: codeWrapper)
      ]));
    }
  9. Render Mermaid diagrams in markdown

    main

    The markdown_widget package supports Mermaid syntax, allowing you to render complex diagrams and flowcharts directly from text within your markdown content. To use this feature, wrap your mermaid code blocks with the ```mermaid language identifier. Supported diagram types include Flowcharts, Sequence Diagrams, State Diagrams, Entity Relationship Diagrams, User Journeys, Git Graphs, Mindmaps, and Timelines.

    graph TD
        A[Start] --> B{Is it working?}
        B -->|Yes| C[Great!]
        B -->|No| D[Debug]
        D --> B
        C --> E[Finish]
  10. Enable Mermaid diagram support

    main

    To render Mermaid diagrams in MarkdownWidget, you must wrap your configuration using the createMermaidWrapper function. This wrapper intercepts code blocks marked with language="mermaid" and uses the Kroki.io API to fetch PNG renders of the diagrams.

    Integration requires passing the result of createMermaidWrapper into a PreConfig object, which is then added to the MarkdownConfig via the configs list.

    final preConfig = PreConfig(
      wrapper: createMermaidWrapper(
        config: const MermaidConfig(),
        isDark: isDark,
        preConfig: preConfig,
      ),
    );
    
    MarkdownWidget(
      data: markdown,
      config: config.copy(configs: [preConfig]),
    )
  11. Support Mermaid diagrams

    main

    Mermaid diagrams (flowcharts, sequence diagrams, etc.) can be supported by using a custom wrapper via createMermaidWrapper. This allows for interactive features like full-screen viewing, zooming, and theme switching.

    import 'package:markdown_widget/markdown_widget.dart';
    import 'markdown_custom/mermaid.dart';
    
    // Basic usage
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final preConfig = PreConfig(
      wrapper: createMermaidWrapper(
        config: const MermaidConfig(),
        isDark: isDark,
        preConfig: preConfig,
      ),
    );
    
    MarkdownWidget(
      data: markdown,
      config: config.copy(configs: [preConfig]),
    )
    
    // Custom configuration
    final preConfig = PreConfig(
      wrapper: createMermaidWrapper(
        config: MermaidConfig(
          displayMode: MermaidDisplayMode.codeAndDiagram,
          diagramPadding: EdgeInsets.all(16.0),
          showLoadingIndicator: true,
        ),
        isDark: isDark,
        preConfig: preConfig,
      ),
    );
    import 'package:markdown_widget/markdown_widget.dart';
    import 'markdown_custom/mermaid.dart';
    
    // 基本用法
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final preConfig = PreConfig(
      wrapper: createMermaidWrapper(
        config: const MermaidConfig(),
        isDark: isDark,
        preConfig: preConfig,
      ),
    );
    
    MarkdownWidget(
      data: markdown,
      config: config.copy(configs: [preConfig]),
    )
    
    // 自定义配置
    final preConfig = PreConfig(
      wrapper: createMermaidWrapper(
        config: MermaidConfig(
          displayMode: MermaidDisplayMode.codeAndDiagram,
          diagramPadding: EdgeInsets.all(16.0),
          showLoadingIndicator: true,
        ),
        isDark: isDark,
        preConfig: preConfig,
      ),
    );
  12. Basic Usage of MarkdownWidget

    main

    The primary way to render markdown is using the MarkdownWidget. It is a simple, easy-to-use component that supports TOC, code highlighting, and multiple platforms.

    import 'package:flutter/material.dart';
    import 'package:markdown_widget/markdown_widget.dart';
    
    class MarkdownPage extends StatelessWidget {
      final String data;
    
      MarkdownPage(this.data);
    
      @override
      Widget build(BuildContext context) => Scaffold(body: buildMarkdown());
    
      Widget buildMarkdown() => MarkdownWidget(data: data);
    }
    import 'package:flutter/material.dart';
    import 'package:markdown_widget/markdown_widget.dart';
    
    class MarkdownPage extends StatelessWidget {
      final String data;
    
      MarkdownPage(this.data);
    
      @override
      Widget build(BuildContext context) => Scaffold(body: buildMarkdown());
    
      Widget buildMarkdown() => MarkdownWidget(data: data);
    }