ChemPal Documentation - v1.6.0
    Preparing search index...

    Function parseReleaseNotes

    • Parses a GitHub release body into headed groups of bullets for display. Recognizes ##/### headings and -/*/+ bullets, ignoring prose, and drops the trailing "Full Changelog" link GitHub appends. Continuation lines of a wrapped bullet are folded into that bullet.

      Parameters

      • body: undefined | string

        The release body markdown, if any.

      Returns ReleaseSection[]

      Sections in document order; empty when there's nothing to show.

      parseReleaseNotes("### Added\n\n- Options page\n- Advanced mode\n");
      // [{ title: "Added", items: ["Options page", "Advanced mode"] }]
      export function parseReleaseNotes(body: string | undefined): ReleaseSection[] {
      if (!body) return [];

      const sections: ReleaseSection[] = [];
      let current: ReleaseSection | undefined;

      for (const rawLine of body.split(/\r?\n/)) {
      const line = rawLine.trim();
      // GitHub appends this to generated notes; it's noise in the modal.
      if (/^\*{0,2}Full Changelog\*{0,2}\s*:/i.test(line)) continue;

      const heading = /^#{2,4}\s+(.*\S)\s*$/.exec(line);
      if (heading) {
      current = { title: stripMarkdown(heading[1]), items: [] };
      sections.push(current);
      continue;
      }

      const bullet = /^[-*+]\s+(.*)$/.exec(line);
      if (bullet) {
      const item = stripMarkdown(bullet[1]);
      if (!item) continue;
      current ??= { items: [] };
      if (sections.at(-1) !== current) sections.push(current);
      if (current.items.length < MAX_NOTE_ITEMS_PER_SECTION) {
      current.items.push(item.slice(0, MAX_NOTE_ITEM_LENGTH));
      }
      continue;
      }

      // A wrapped continuation of the previous bullet.
      const items = current?.items;
      if (line && items && items.length > 0) {
      const merged = `${items[items.length - 1]} ${stripMarkdown(line)}`.trim();
      items[items.length - 1] = merged.slice(0, MAX_NOTE_ITEM_LENGTH);
      }
      }

      return sections.filter((section) => section.items.length > 0).slice(0, MAX_NOTE_SECTIONS);
      }