sectionsOf function

List<Map<String, Object?>> sectionsOf(
  1. String body, {
  2. int maxSectionLength = 1200,
})

Splits a markdown body into {h: heading, a: anchor, b: body} records.

Text before the first heading becomes a leading section with no anchor or heading, so a page's intro is searchable too. maxSectionLength caps how much of each section's body is kept, since the index is downloaded whole.

Implementation

List<Map<String, Object?>> sectionsOf(
  String body, {
  int maxSectionLength = 1200,
}) {
  final sections = <Map<String, Object?>>[];
  var heading = '';
  var anchor = '';
  final buffer = StringBuffer();
  var inFence = false;

  void flush() {
    final text = plainText(buffer.toString());
    buffer.clear();
    if (heading.isEmpty && text.isEmpty) return;
    sections.add({
      if (heading.isNotEmpty) 'h': heading,
      if (anchor.isNotEmpty) 'a': anchor,
      'b': text.length > maxSectionLength
          ? text.substring(0, maxSectionLength)
          : text,
    });
  }

  // Commented-out prose is not on the page, so a `##` inside `<!-- -->` is not
  // a section. This has to happen before the split, not in [plainText]: by
  // the time that runs, a commented heading has already become an index
  // entry deep-linking to an anchor the built page does not contain.
  final visible = body.replaceAll(RegExp('<!--.*?-->', dotAll: true), '');

  for (final line in visible.split('\n')) {
    if (line.trimLeft().startsWith('```')) {
      inFence = !inFence;
      continue;
    }

    // Headings inside a fenced block are shell comments, not sections.
    final match = inFence
        ? null
        : RegExp(r'^(#{2,3})\s+(.*)$').firstMatch(line);
    if (match == null) {
      buffer.writeln(line);
      continue;
    }

    flush();
    heading = plainText(match.group(2)!);
    anchor = anchorFor(match.group(2)!);
  }
  flush();

  return sections;
}