shelving 1.222.0 → 1.222.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import { anyMatch, requirePath, splitPath } from "../util/index.js";
5
5
  import { requireSlug } from "../util/string.js";
6
6
  import { Extractor, mergeTreeElements } from "./Extractor.js";
7
7
  import { FileExtractor } from "./FileExtractor.js";
8
- import { MarkdownExtractor } from "./MarkdownExtractor.js";
8
+ import { MarkupExtractor } from "./MarkupExtractor.js";
9
9
  import { TypescriptExtractor } from "./TypescriptExtractor.js";
10
10
  /**
11
11
  * Default list of filenames patterns treated as the directory's index file.
@@ -15,7 +15,7 @@ import { TypescriptExtractor } from "./TypescriptExtractor.js";
15
15
  const DEFAULT_INDEX = [/^readme\.txt$/i, /^readme\.md$/i, /^index\.md$/i, /^index\.ts$/i, /^index\.tsx$/i];
16
16
  /** Default file extractor dispatch by extension. */
17
17
  const DEFAULT_EXTRACTORS = {
18
- md: new MarkdownExtractor(),
18
+ md: new MarkupExtractor(),
19
19
  ts: new TypescriptExtractor(),
20
20
  tsx: new TypescriptExtractor(),
21
21
  txt: new FileExtractor(),
@@ -11,7 +11,7 @@ export declare abstract class Extractor<I, O extends TreeElement = TreeElement>
11
11
  * Priority used to resolve same-key collisions when merging elements.
12
12
  * - Higher-priority elements contribute their `title`, `description`, `path` to the merged result.
13
13
  * - Higher-priority elements a prefixed (rather than suffixed) in `children` and `content` when merged.
14
- * - Defaults to `0`; subclasses override (e.g. `MarkdownExtractor` is `10`).
14
+ * - Defaults to `0`; subclasses override (e.g. `MarkupExtractor` is `10`).
15
15
  */
16
16
  readonly priority: number;
17
17
  }
@@ -9,7 +9,7 @@ export class Extractor {
9
9
  * Priority used to resolve same-key collisions when merging elements.
10
10
  * - Higher-priority elements contribute their `title`, `description`, `path` to the merged result.
11
11
  * - Higher-priority elements a prefixed (rather than suffixed) in `children` and `content` when merged.
12
- * - Defaults to `0`; subclasses override (e.g. `MarkdownExtractor` is `10`).
12
+ * - Defaults to `0`; subclasses override (e.g. `MarkupExtractor` is `10`).
13
13
  */
14
14
  priority = 0;
15
15
  }
@@ -7,8 +7,8 @@ import { Extractor } from "./Extractor.js";
7
7
  * - Sets `source` to the file's absolute path (`BunFile.name`); throws `RequiredError` if missing or non-absolute.
8
8
  * - Sets `name` to the basename without extension, preserving case (e.g. `"OptionalSchema"` from `"OptionalSchema.ts"`); URL paths use `name`.
9
9
  * - Sets `key` to the slugified `name` (e.g. `"optionalschema"`) — only used by React for reconciliation and by `DirectoryExtractor` to merge same-key siblings (e.g. `TEMPLATE.md` + `template.ts`).
10
- * - Does NOT set `title` — `title` is only set by subclasses that have a confident source for one (e.g. `MarkdownExtractor` uses the first `<h1>`). Renderers fall back to `name` when missing.
11
- * - Subclasses (e.g. `MarkdownExtractor`, `TypescriptExtractor`) override `extractProps()` to parse the content into richer elements.
10
+ * - Does NOT set `title` — `title` is only set by subclasses that have a confident source for one (e.g. `MarkupExtractor` uses the first `<h1>`). Renderers fall back to `name` when missing.
11
+ * - Subclasses (e.g. `MarkupExtractor`, `TypescriptExtractor`) override `extractProps()` to parse the content into richer elements.
12
12
  */
13
13
  export declare class FileExtractor extends Extractor<BunFile, FileElement> {
14
14
  extract(file: BunFile): Promise<FileElement>;
@@ -9,8 +9,8 @@ import { Extractor } from "./Extractor.js";
9
9
  * - Sets `source` to the file's absolute path (`BunFile.name`); throws `RequiredError` if missing or non-absolute.
10
10
  * - Sets `name` to the basename without extension, preserving case (e.g. `"OptionalSchema"` from `"OptionalSchema.ts"`); URL paths use `name`.
11
11
  * - Sets `key` to the slugified `name` (e.g. `"optionalschema"`) — only used by React for reconciliation and by `DirectoryExtractor` to merge same-key siblings (e.g. `TEMPLATE.md` + `template.ts`).
12
- * - Does NOT set `title` — `title` is only set by subclasses that have a confident source for one (e.g. `MarkdownExtractor` uses the first `<h1>`). Renderers fall back to `name` when missing.
13
- * - Subclasses (e.g. `MarkdownExtractor`, `TypescriptExtractor`) override `extractProps()` to parse the content into richer elements.
12
+ * - Does NOT set `title` — `title` is only set by subclasses that have a confident source for one (e.g. `MarkupExtractor` uses the first `<h1>`). Renderers fall back to `name` when missing.
13
+ * - Subclasses (e.g. `MarkupExtractor`, `TypescriptExtractor`) override `extractProps()` to parse the content into richer elements.
14
14
  */
15
15
  export class FileExtractor extends Extractor {
16
16
  async extract(file) {
@@ -0,0 +1,31 @@
1
+ import { type FileElementProps } from "../util/element.js";
2
+ import { FileExtractor } from "./FileExtractor.js";
3
+ /**
4
+ * File extractor for Markdown files.
5
+ * - Stores the markdown text as `content`; rendering happens at output time via `<Markup>`.
6
+ * - Sets `title` from the first `# h1` heading if one is present — otherwise leaves it undefined
7
+ * (a confident title only).
8
+ * - When a `title` is found, strips the leading `# h1` from `content` so renderers (which show
9
+ * `title` separately) don't display the heading twice.
10
+ * - Sets `description` to the first prose paragraph as a plain-text summary (used for card listings and `<meta>`).
11
+ */
12
+ export declare class MarkupExtractor extends FileExtractor {
13
+ /** Markdown contributes the canonical title/path when merging same-key elements. */
14
+ readonly priority = 10;
15
+ extractProps(name: string, text: string): Partial<FileElementProps> & {
16
+ name: string;
17
+ };
18
+ }
19
+ /**
20
+ * Parse a markdown source string once and derive its `title` and `description` in a single pass.
21
+ * - `title` — plain text of the first `# h1` heading, or `undefined` if there is none.
22
+ * - `description` — plain-text summary of the first prose paragraph, or `undefined` if there is none.
23
+ * - Both query the parsed markup tree, so inline syntax (`` `code` ``, `*emphasis*`, links) resolves to clean plain text.
24
+ *
25
+ * @param text The markdown source string (a markdown file's text, or a JSDoc description).
26
+ * @returns An object whose `title` and `description` are each a plain string, or `undefined` when absent.
27
+ */
28
+ export declare function extractMarkdownProps(text: string): {
29
+ title: string | undefined;
30
+ description: string | undefined;
31
+ };
@@ -0,0 +1,52 @@
1
+ import { MARKUP_PARSER } from "../markup/MarkupParser.js";
2
+ import { getElementText, walkElements } from "../util/element.js";
3
+ import { FileExtractor } from "./FileExtractor.js";
4
+ /**
5
+ * File extractor for Markdown files.
6
+ * - Stores the markdown text as `content`; rendering happens at output time via `<Markup>`.
7
+ * - Sets `title` from the first `# h1` heading if one is present — otherwise leaves it undefined
8
+ * (a confident title only).
9
+ * - When a `title` is found, strips the leading `# h1` from `content` so renderers (which show
10
+ * `title` separately) don't display the heading twice.
11
+ * - Sets `description` to the first prose paragraph as a plain-text summary (used for card listings and `<meta>`).
12
+ */
13
+ export class MarkupExtractor extends FileExtractor {
14
+ /** Markdown contributes the canonical title/path when merging same-key elements. */
15
+ priority = 10;
16
+ extractProps(name, text) {
17
+ const { title, description } = extractMarkdownProps(text);
18
+ // The title `# h1` is surfaced separately as `title`, so strip it from the body to avoid rendering it twice.
19
+ return { name, title, description, content: title ? _stripTitle(text) : text };
20
+ }
21
+ }
22
+ /**
23
+ * Parse a markdown source string once and derive its `title` and `description` in a single pass.
24
+ * - `title` — plain text of the first `# h1` heading, or `undefined` if there is none.
25
+ * - `description` — plain-text summary of the first prose paragraph, or `undefined` if there is none.
26
+ * - Both query the parsed markup tree, so inline syntax (`` `code` ``, `*emphasis*`, links) resolves to clean plain text.
27
+ *
28
+ * @param text The markdown source string (a markdown file's text, or a JSDoc description).
29
+ * @returns An object whose `title` and `description` are each a plain string, or `undefined` when absent.
30
+ */
31
+ export function extractMarkdownProps(text) {
32
+ let title;
33
+ let description;
34
+ // Walk the top-level block elements once, claiming the first `h1` as the title and the first `p` as the description.
35
+ for (const element of walkElements(MARKUP_PARSER.parse(text))) {
36
+ if (!title && element.type === "h1")
37
+ title = _plain(element);
38
+ else if (!description && element.type === "p")
39
+ description = _plain(element);
40
+ if (title && description)
41
+ break;
42
+ }
43
+ return { title, description };
44
+ }
45
+ /** Flatten an element to a single-line plain-text summary, or `undefined` if it has no text. */
46
+ function _plain(element) {
47
+ return getElementText(element).replace(/\s+/g, " ").trim() || undefined;
48
+ }
49
+ /** Strip a leading `# h1` heading (and any blank lines after it) from markdown text. */
50
+ function _stripTitle(text) {
51
+ return text.replace(/^\s*#[^\n\S]+\S[^\n]*(?:\n+|$)/, "");
52
+ }
@@ -1,7 +1,7 @@
1
1
  import ts from "typescript";
2
2
  import { requireSlug } from "../util/string.js";
3
3
  import { FileExtractor } from "./FileExtractor.js";
4
- import { extractMarkdownDescription } from "./MarkdownExtractor.js";
4
+ import { extractMarkdownProps } from "./MarkupExtractor.js";
5
5
  /**
6
6
  * File extractor that parses a TypeScript source file into a tree element.
7
7
  * - Uses the TypeScript compiler API to parse the AST.
@@ -27,7 +27,7 @@ export class TypescriptExtractor extends FileExtractor {
27
27
  }
28
28
  // The file element itself gets no `title` — a TS source file has no confident title source (the filename isn't one),
29
29
  // so renderers fall back to `name`. The `tree-documentation` children each carry their own `title`.
30
- return { name, description: extractMarkdownDescription(content ?? ""), content, children: Array.from(byKey.values()) };
30
+ return { name, description: extractMarkdownProps(content ?? "").description, content, children: Array.from(byKey.values()) };
31
31
  }
32
32
  }
33
33
  /** Merge a newly-extracted overload into the existing documentation element with the same key. */
@@ -102,7 +102,7 @@ function _extractStatement(statement, source) {
102
102
  // Functions read as callable with `()`; other kinds use the bare name.
103
103
  title: kind === "function" ? `${name}()` : name,
104
104
  kind,
105
- description: extractMarkdownDescription(jsDoc?.description ?? ""),
105
+ description: extractMarkdownProps(jsDoc?.description ?? "").description,
106
106
  content: _buildJSDocContent(jsDoc?.description, jsDoc?.unhandled),
107
107
  signatures: signature ? [signature] : undefined,
108
108
  params,
@@ -239,7 +239,7 @@ function _getClassMembers(statement, source) {
239
239
  props: {
240
240
  name,
241
241
  title: `${name}()`,
242
- description: extractMarkdownDescription(memberJSDoc?.description ?? ""),
242
+ description: extractMarkdownProps(memberJSDoc?.description ?? "").description,
243
243
  content,
244
244
  kind: "method",
245
245
  signatures: [signature],
@@ -255,7 +255,7 @@ function _getClassMembers(statement, source) {
255
255
  props: {
256
256
  name,
257
257
  title: name,
258
- description: extractMarkdownDescription(memberJSDoc?.description ?? ""),
258
+ description: extractMarkdownProps(memberJSDoc?.description ?? "").description,
259
259
  content,
260
260
  kind: "property",
261
261
  signatures: type ? [type] : undefined,
@@ -1,5 +1,5 @@
1
1
  export * from "./DirectoryExtractor.js";
2
2
  export * from "./Extractor.js";
3
3
  export * from "./FileExtractor.js";
4
- export * from "./MarkdownExtractor.js";
4
+ export * from "./MarkupExtractor.js";
5
5
  export * from "./TypescriptExtractor.js";
package/extract/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./DirectoryExtractor.js";
2
2
  export * from "./Extractor.js";
3
3
  export * from "./FileExtractor.js";
4
- export * from "./MarkdownExtractor.js";
4
+ export * from "./MarkupExtractor.js";
5
5
  export * from "./TypescriptExtractor.js";
@@ -238,11 +238,16 @@ function _findFrom(rule, masked, input, from, higher) {
238
238
  lo = wallEnd;
239
239
  }
240
240
  }
241
- /** Blank the `[start, end)` region of `text` every character becomes a space, except newlines. */
241
+ // Placeholder a claimed region is blanked to: non-whitespace and non-word, so lower tiers see a
242
+ // masked region as opaque *content* rather than whitespace. Blanking to a space made a leading or
243
+ // trailing code span look like whitespace to inline emphasis (which rejects whitespace at its
244
+ // start/end), so `**`code`**` failed to form. Newlines are kept so block structure survives.
245
+ const _MASK_CHAR = "\u0000";
246
+ /** Blank the `[start, end)` region of `text` — every character becomes `_MASK_CHAR`, except newlines. */
242
247
  function _mask(text, start, end) {
243
248
  let blanked = "";
244
249
  for (let i = start; i < end; i++)
245
- blanked += text[i] === "\n" ? "\n" : " ";
250
+ blanked += text[i] === "\n" ? "\n" : _MASK_CHAR;
246
251
  return `${text.slice(0, start)}${blanked}${text.slice(end)}`;
247
252
  }
248
253
  /** MarkupParser sentinel with the default markup rules */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.222.0",
3
+ "version": "1.222.2",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Prose } from "../block/Prose.js";
3
+ import { Title } from "../block/Title.js";
3
4
  import { Markup } from "../misc/Markup.js";
4
5
  import { requireMeta } from "../misc/MetaContext.js";
5
6
  import { Page } from "../page/Page.js";
@@ -8,5 +9,5 @@ import { TreeCards } from "../tree/TreeCards.js";
8
9
  export function DirectoryPage({ title, name, description, content, children }) {
9
10
  const { url } = requireMeta();
10
11
  const path = url?.pathname ?? "/";
11
- return (_jsxs(Page, { title: title ?? name, description: description, children: [content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { path: path, children: children })] }));
12
+ return (_jsxs(Page, { title: title ?? name, description: description, children: [_jsx(Title, { children: title ?? name }), content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { path: path, children: children })] }));
12
13
  }
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { DirectoryElementProps } from "../../util/element.js";
3
3
  import { Prose } from "../block/Prose.js";
4
+ import { Title } from "../block/Title.js";
4
5
  import { Markup } from "../misc/Markup.js";
5
6
  import { requireMeta } from "../misc/MetaContext.js";
6
7
  import { Page } from "../page/Page.js";
@@ -12,6 +13,7 @@ export function DirectoryPage({ title, name, description, content, children }: D
12
13
  const path = url?.pathname ?? "/";
13
14
  return (
14
15
  <Page title={title ?? name} description={description}>
16
+ <Title>{title ?? name}</Title>
15
17
  {content && (
16
18
  <Prose>
17
19
  <Markup>{content}</Markup>
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Prose } from "../block/Prose.js";
3
+ import { Title } from "../block/Title.js";
3
4
  import { Markup } from "../misc/Markup.js";
4
5
  import { requireMeta } from "../misc/MetaContext.js";
5
6
  import { Page } from "../page/Page.js";
@@ -8,5 +9,5 @@ import { TreeCards } from "../tree/TreeCards.js";
8
9
  export function FilePage({ title, name, description, content, children }) {
9
10
  const { url } = requireMeta();
10
11
  const path = url?.pathname ?? "/";
11
- return (_jsxs(Page, { title: title ?? name, description: description, children: [content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { path: path, children: children })] }));
12
+ return (_jsxs(Page, { title: title ?? name, description: description, children: [_jsx(Title, { children: title ?? name }), content && (_jsx(Prose, { children: _jsx(Markup, { children: content }) })), _jsx(TreeCards, { path: path, children: children })] }));
12
13
  }
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { FileElementProps } from "../../util/element.js";
3
3
  import { Prose } from "../block/Prose.js";
4
+ import { Title } from "../block/Title.js";
4
5
  import { Markup } from "../misc/Markup.js";
5
6
  import { requireMeta } from "../misc/MetaContext.js";
6
7
  import { Page } from "../page/Page.js";
@@ -12,6 +13,7 @@ export function FilePage({ title, name, description, content, children }: FileEl
12
13
  const path = url?.pathname ?? "/";
13
14
  return (
14
15
  <Page title={title ?? name} description={description}>
16
+ <Title>{title ?? name}</Title>
15
17
  {content && (
16
18
  <Prose>
17
19
  <Markup>{content}</Markup>
package/util/element.d.ts CHANGED
@@ -41,7 +41,7 @@ export interface TreeElementProps extends ElementProps {
41
41
  /**
42
42
  * Markup source string that should be rendered as the element's visible body content.
43
43
  * - Rendered via the `<Markup>` component (typically inside a `<Prose>` wrapper).
44
- * - For Markdown files this is the file's text; for TypeScript symbols this is the JSDoc description.
44
+ * - For Markdown files this is the file's body (the title `# h1` is lifted into `title`); for TypeScript symbols this is the JSDoc description.
45
45
  */
46
46
  readonly content?: string | undefined;
47
47
  /** Children of a tree element must be other tree elements. */
@@ -153,6 +153,7 @@ export declare function isElement(value: unknown): value is Element;
153
153
  export declare function isElements(value: unknown): value is Elements;
154
154
  /**
155
155
  * Strip all tags from elements to produce a plain text string.
156
+ * - A `<br>` element becomes a newline (`\n`) — matching DOM `innerText`, so words either side of a line break don't fuse together.
156
157
  *
157
158
  * @param elements An element, a plain string, or null/undefined (or an array of those things).
158
159
  * @returns The combined string made from the elements.
package/util/element.js CHANGED
@@ -11,6 +11,7 @@ export function isElements(value) {
11
11
  }
12
12
  /**
13
13
  * Strip all tags from elements to produce a plain text string.
14
+ * - A `<br>` element becomes a newline (`\n`) — matching DOM `innerText`, so words either side of a line break don't fuse together.
14
15
  *
15
16
  * @param elements An element, a plain string, or null/undefined (or an array of those things).
16
17
  * @returns The combined string made from the elements.
@@ -20,8 +21,12 @@ export function isElements(value) {
20
21
  export function getElementText(elements) {
21
22
  if (typeof elements === "string")
22
23
  return elements;
23
- if (isElement(elements))
24
+ if (isElement(elements)) {
25
+ // A `<br>` carries no children but renders as a line break — emit `\n` so adjacent words stay separated.
26
+ if (elements.type === "br")
27
+ return "\n";
24
28
  return getElementText(elements.props.children);
29
+ }
25
30
  // Iterate the collection directly — `walkElements()` skips loose strings, so it would drop text that sits alongside elements.
26
31
  if (isIterable(elements)) {
27
32
  let text = "";
@@ -1,28 +0,0 @@
1
- import { type FileElementProps } from "../util/element.js";
2
- import { FileExtractor } from "./FileExtractor.js";
3
- /**
4
- * File extractor for Markdown files.
5
- * - Stores the raw markdown text as `content`; rendering happens at output time via `<Markup>`.
6
- * - Sets `title` from the first `# h1` heading if one is present — otherwise leaves it undefined
7
- * (a confident title only).
8
- * - Sets `description` to the first prose paragraph as a plain-text summary (used for card listings and `<meta>`).
9
- */
10
- export declare class MarkdownExtractor extends FileExtractor {
11
- /** Markdown contributes the canonical title/path when merging same-key elements. */
12
- readonly priority = 10;
13
- extractProps(name: string, text: string): Partial<FileElementProps> & {
14
- name: string;
15
- };
16
- }
17
- /**
18
- * Find the first `# h1` heading in a markdown source string and return its text, or `undefined` if none.
19
- * - Looks for a line starting with a single `#` followed by whitespace; doesn't match `##`+.
20
- */
21
- export declare function extractMarkdownTitle(text: string): string | undefined;
22
- /**
23
- * Find the first prose paragraph in a markdown source string and return it as a plain-text summary, or `undefined` if none.
24
- * - Skips headings, fenced code blocks, and blank lines, then collects the first ordinary paragraph.
25
- * - Renders that paragraph as markup and strips every tag, so inline syntax (`` `code` ``, `*emphasis*`, links) becomes plain text.
26
- * - Collapses internal whitespace so the result is a single line suitable for a `description` / `<meta>` summary.
27
- */
28
- export declare function extractMarkdownDescription(text: string): string | undefined;
@@ -1,58 +0,0 @@
1
- import { MARKUP_PARSER } from "../markup/MarkupParser.js";
2
- import { getElementText } from "../util/element.js";
3
- import { FileExtractor } from "./FileExtractor.js";
4
- /**
5
- * File extractor for Markdown files.
6
- * - Stores the raw markdown text as `content`; rendering happens at output time via `<Markup>`.
7
- * - Sets `title` from the first `# h1` heading if one is present — otherwise leaves it undefined
8
- * (a confident title only).
9
- * - Sets `description` to the first prose paragraph as a plain-text summary (used for card listings and `<meta>`).
10
- */
11
- export class MarkdownExtractor extends FileExtractor {
12
- /** Markdown contributes the canonical title/path when merging same-key elements. */
13
- priority = 10;
14
- extractProps(name, text) {
15
- return { name, title: extractMarkdownTitle(text), description: extractMarkdownDescription(text), content: text };
16
- }
17
- }
18
- /**
19
- * Find the first `# h1` heading in a markdown source string and return its text, or `undefined` if none.
20
- * - Looks for a line starting with a single `#` followed by whitespace; doesn't match `##`+.
21
- */
22
- export function extractMarkdownTitle(text) {
23
- const match = text.match(/^#\s+(.+?)\s*$/m);
24
- return match?.[1];
25
- }
26
- /**
27
- * Find the first prose paragraph in a markdown source string and return it as a plain-text summary, or `undefined` if none.
28
- * - Skips headings, fenced code blocks, and blank lines, then collects the first ordinary paragraph.
29
- * - Renders that paragraph as markup and strips every tag, so inline syntax (`` `code` ``, `*emphasis*`, links) becomes plain text.
30
- * - Collapses internal whitespace so the result is a single line suitable for a `description` / `<meta>` summary.
31
- */
32
- export function extractMarkdownDescription(text) {
33
- const paragraph = [];
34
- let fenced = false;
35
- for (const line of text.split("\n")) {
36
- const trimmed = line.trim();
37
- // Toggle in/out of fenced code blocks — never treat their contents as the summary.
38
- if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
39
- fenced = !fenced;
40
- continue;
41
- }
42
- if (fenced)
43
- continue;
44
- // A blank line or heading ends the first paragraph (or is skipped while still searching for it).
45
- if (!trimmed || trimmed.startsWith("#")) {
46
- if (paragraph.length)
47
- break;
48
- continue;
49
- }
50
- paragraph.push(trimmed);
51
- }
52
- if (!paragraph.length)
53
- return;
54
- // Render the paragraph as markup then strip every tag, so inline syntax resolves to clean plain text.
55
- const rendered = MARKUP_PARSER.parse(paragraph.join(" "));
56
- const summary = getElementText(rendered).replace(/\s+/g, " ").trim();
57
- return summary || undefined;
58
- }