ropegeo-common 1.24.0 → 1.24.1

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.
@@ -3,13 +3,15 @@ import { BetaSectionExcerpt } from './betaSectionExcerpt';
3
3
  import { BetaSectionImage } from './betaSectionImage';
4
4
  /**
5
5
  * An excerpt split into its verbatim `body` and the surrounding section
6
- * context. Consumers render `before` and `after` de-emphasized (e.g. faded)
7
- * relative to `body`; concatenating the three yields well-formed HTML.
6
+ * context. Each of `before`, `body`, and `after` is independently well-formed
7
+ * HTML, so consumers can render them as separate (e.g. stacked) blocks and
8
+ * de-emphasize the context relative to the body. `before`/`after` are omitted
9
+ * when there is no surrounding context (or it would be empty/whitespace).
8
10
  */
9
11
  export type ExcerptHtml = {
10
- before: string;
12
+ before?: string;
11
13
  body: string;
12
- after: string;
14
+ after?: string;
13
15
  };
14
16
  export declare function registerBetaSectionParser(fetchType: FetchType, parse: (body: unknown) => BetaSection): void;
15
17
  export declare abstract class BetaSection {
@@ -1 +1 @@
1
- {"version":3,"file":"betaSection.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/betaSection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAUtD;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAIF,wBAAgB,yBAAyB,CACrC,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,WAAW,GACtC,IAAI,CAEN;AAED,8BAAsB,WAAW;IAC7B,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,IAAI,CAAC;IACzB,mEAAmE;IACnE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAElB,SAAS,aACL,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,IAAI,EACxB,EAAE,GAAE,MAAM,GAAG,IAAW;IAS5B;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB;IAWtE;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW;IA0CvD,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,WAAW;IA0B1E,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAS9E,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAS9E,SAAS,CAAC,MAAM,CAAC,uBAAuB,CACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,IAAI;IAeP,SAAS,CAAC,MAAM,CAAC,qBAAqB,CAClC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,SAAS,GAC7B,IAAI;IAcP,OAAO,CAAC,MAAM,CAAC,4BAA4B;IAgB3C,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,iBAAiB,EAAE,SAAS,GAC7B,gBAAgB,EAAE;IAYrB,SAAS,CAAC,MAAM,CAAC,oBAAoB,CACjC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,SAAS,EAAE,OAAO,GACnB,IAAI;CAeV"}
1
+ {"version":3,"file":"betaSection.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/betaSection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAUtD;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,wBAAgB,yBAAyB,CACrC,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,WAAW,GACtC,IAAI,CAEN;AAED,8BAAsB,WAAW;IAC7B,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,IAAI,CAAC;IACzB,mEAAmE;IACnE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAElB,SAAS,aACL,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,IAAI,EACxB,EAAE,GAAE,MAAM,GAAG,IAAW;IAS5B;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB;IAWtE;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW;IA8CvD,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,WAAW;IA0B1E,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAS9E,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAS9E,SAAS,CAAC,MAAM,CAAC,uBAAuB,CACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,IAAI;IAeP,SAAS,CAAC,MAAM,CAAC,qBAAqB,CAClC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,SAAS,GAC7B,IAAI;IAcP,OAAO,CAAC,MAAM,CAAC,4BAA4B;IAgB3C,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,iBAAiB,EAAE,SAAS,GAC7B,gBAAgB,EAAE;IAYrB,SAAS,CAAC,MAAM,CAAC,oBAAoB,CACjC,MAAM,EAAE,gBAAgB,EAAE,EAC1B,SAAS,EAAE,OAAO,GACnB,IAAI;CAeV"}
@@ -47,20 +47,31 @@ class BetaSection {
47
47
  (0, formatExcerptHtml_1.isSafeTextBoundary)(this.text, excerpt.start) &&
48
48
  (0, formatExcerptHtml_1.isSafeTextBoundary)(this.text, excerpt.end);
49
49
  if (!validOffsets)
50
- return { before: '', body: excerpt.toHtml(), after: '' };
50
+ return { body: excerpt.toHtml() };
51
51
  const excerptStart = excerpt.start;
52
52
  const excerptEnd = excerpt.end;
53
53
  const characters = (0, formatExcerptHtml_1.visibleCharacters)(this.text);
54
54
  const structuredContext = (0, formatExcerptHtml_1.listContext)(this.text, excerptStart, excerptEnd, characters);
55
- const before = structuredContext?.before ??
56
- this.text.slice((0, formatExcerptHtml_1.previousSentenceStart)(this.text, excerptStart, characters), excerptStart);
57
- const after = structuredContext?.after ??
58
- this.text.slice(excerptEnd, (0, formatExcerptHtml_1.nextSentenceEnd)(this.text, excerptEnd, characters));
59
- return (0, normalizeExcerptHtml_1.normalizeExcerptListHtmlParts)({
60
- before,
55
+ const parts = structuredContext ?? {
56
+ before: this.text
57
+ .slice((0, formatExcerptHtml_1.previousSentenceStart)(this.text, excerptStart, characters), excerptStart)
58
+ .trim(),
61
59
  body: excerpt.text,
62
- after,
63
- });
60
+ after: this.text
61
+ .slice(excerptEnd, (0, formatExcerptHtml_1.nextSentenceEnd)(this.text, excerptEnd, characters))
62
+ .trim(),
63
+ };
64
+ // We have the source list here, so keep ordered lists ordered; their
65
+ // `start` ordinals are already aligned to the source by `listContext`.
66
+ const options = { preserveOrderedLists: true };
67
+ const result = { body: (0, normalizeExcerptHtml_1.normalizeExcerptListHtml)(parts.body, options) };
68
+ const before = (0, normalizeExcerptHtml_1.normalizeExcerptListHtml)(parts.before, options);
69
+ if (before.trim().length > 0)
70
+ result.before = before;
71
+ const after = (0, normalizeExcerptHtml_1.normalizeExcerptListHtml)(parts.after, options);
72
+ if (after.trim().length > 0)
73
+ result.after = after;
74
+ return result;
64
75
  }
65
76
  static fromResponseBody(body, fetchType) {
66
77
  if (body == null || typeof body !== 'object') {
@@ -7,8 +7,16 @@ export declare function visibleCharacters(html: string): VisibleCharacter[];
7
7
  export declare function isSafeTextBoundary(html: string, offset: number): boolean;
8
8
  export declare function previousSentenceStart(html: string, anchor: number, characters: VisibleCharacter[]): number;
9
9
  export declare function nextSentenceEnd(html: string, anchor: number, characters: VisibleCharacter[]): number;
10
+ /**
11
+ * When an excerpt is a run of list items, returns the excerpt and its adjacent
12
+ * context split into three independently well-formed HTML fragments. Each
13
+ * fragment stands on its own: the surrounding list items (or the sentence
14
+ * before/after the list) become complete lists/prose so they can be rendered as
15
+ * separate stacked blocks.
16
+ */
10
17
  export declare function listContext(html: string, excerptStart: number, excerptEnd: number, characters: VisibleCharacter[]): {
11
18
  before: string;
19
+ body: string;
12
20
  after: string;
13
21
  } | null;
14
22
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"formatExcerptHtml.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/formatExcerptHtml.ts"],"names":[],"mappings":"AAAA,KAAK,gBAAgB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBzD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAuBlE;AAYD,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAsCxE;AAuCD,wBAAgB,qBAAqB,CACjC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,gBAAgB,EAAE,GAC/B,MAAM,CAkBR;AAED,wBAAgB,eAAe,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,gBAAgB,EAAE,GAC/B,MAAM,CAuBR;AAyCD,wBAAgB,WAAW,CACvB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,gBAAgB,EAAE,GAC/B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA0C1C"}
1
+ {"version":3,"file":"formatExcerptHtml.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/formatExcerptHtml.ts"],"names":[],"mappings":"AAAA,KAAK,gBAAgB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAsBzD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAuBlE;AAYD,2EAA2E;AAC3E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAsCxE;AAuCD,wBAAgB,qBAAqB,CACjC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,gBAAgB,EAAE,GAC/B,MAAM,CAkBR;AAED,wBAAgB,eAAe,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,gBAAgB,EAAE,GAC/B,MAAM,CAuBR;AAoED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACvB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,gBAAgB,EAAE,GAC/B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAwDxD"}
@@ -183,6 +183,33 @@ function findContainingList(html, excerptStart, excerptEnd) {
183
183
  }
184
184
  return (ranges.sort((a, b) => b.openStart - a.openStart || a.closeEnd - b.closeEnd)[0] ?? null);
185
185
  }
186
+ /** `ul` or `ol`, parsed from a list's opening tag. */
187
+ function listContainerTag(openTag) {
188
+ return /^<\s*ol\b/i.test(openTag) ? 'ol' : 'ul';
189
+ }
190
+ /** The 1-based ordinal an ordered list's first item renders as (`start`, default 1). */
191
+ function listStartIndex(openTag) {
192
+ const match = openTag.match(/\bstart\s*=\s*["']?\s*(-?\d+)/i);
193
+ const parsed = match != null ? Number.parseInt(match[1], 10) : Number.NaN;
194
+ return Number.isFinite(parsed) ? parsed : 1;
195
+ }
196
+ /**
197
+ * Wraps list items in a standalone `<ul>`/`<ol>`. Ordered lists carry a `start`
198
+ * attribute (whenever the first item is not ordinal 1) so numbering lines up
199
+ * with the source list even when the excerpt parts are rendered as separate
200
+ * blocks.
201
+ */
202
+ function wrapListItems(tag, itemsHtml, startOrdinal) {
203
+ const startAttr = tag === 'ol' && startOrdinal !== 1 ? ` start="${startOrdinal}"` : '';
204
+ return `<${tag}${startAttr}>${itemsHtml}</${tag}>`;
205
+ }
206
+ /**
207
+ * When an excerpt is a run of list items, returns the excerpt and its adjacent
208
+ * context split into three independently well-formed HTML fragments. Each
209
+ * fragment stands on its own: the surrounding list items (or the sentence
210
+ * before/after the list) become complete lists/prose so they can be rendered as
211
+ * separate stacked blocks.
212
+ */
186
213
  function listContext(html, excerptStart, excerptEnd, characters) {
187
214
  const excerpt = html.slice(excerptStart, excerptEnd).trim();
188
215
  if (!/^<li\b/i.test(excerpt) || !/<\/li\s*>$/i.test(excerpt)) {
@@ -191,6 +218,10 @@ function listContext(html, excerptStart, excerptEnd, characters) {
191
218
  const range = findContainingList(html, excerptStart, excerptEnd);
192
219
  if (range == null)
193
220
  return null;
221
+ const openTag = html.slice(range.openStart, range.openEnd);
222
+ const tag = listContainerTag(openTag);
223
+ // Ordinal of the list's first item so ordered excerpts line up with the source.
224
+ const baseOrdinal = tag === 'ol' ? listStartIndex(openTag) : 1;
194
225
  const listBody = html.slice(range.openEnd, range.closeStart);
195
226
  const items = [...listBody.matchAll(/<li\b[^>]*>[\s\S]*?<\/li\s*>/gi)].map((match) => ({
196
227
  start: range.openEnd + match.index,
@@ -198,13 +229,17 @@ function listContext(html, excerptStart, excerptEnd, characters) {
198
229
  }));
199
230
  const previous = items.filter(({ end }) => end <= excerptStart).at(-1);
200
231
  const next = items.find(({ start }) => start >= excerptEnd);
232
+ const firstBodyIndex = items.findIndex(({ start }) => start >= excerptStart);
233
+ const body = wrapListItems(tag, html.slice(excerptStart, excerptEnd), baseOrdinal + firstBodyIndex);
201
234
  const before = previous != null
202
- ? html.slice(range.openStart, range.openEnd) +
203
- html.slice(previous.start, previous.end)
204
- : html.slice(previousSentenceStart(html, range.openStart, characters), range.openEnd);
235
+ ? wrapListItems(tag, html.slice(previous.start, previous.end), baseOrdinal + items.indexOf(previous))
236
+ : html
237
+ .slice(previousSentenceStart(html, range.openStart, characters), range.openStart)
238
+ .trim();
205
239
  const after = next != null
206
- ? html.slice(next.start, next.end) +
207
- html.slice(range.closeStart, range.closeEnd)
208
- : html.slice(range.closeStart, nextSentenceEnd(html, range.closeEnd, characters));
209
- return { before, after };
240
+ ? wrapListItems(tag, html.slice(next.start, next.end), baseOrdinal + items.indexOf(next))
241
+ : html
242
+ .slice(range.closeEnd, nextSentenceEnd(html, range.closeEnd, characters))
243
+ .trim();
244
+ return { before, body, after };
210
245
  }
@@ -1,18 +1,15 @@
1
- /** Leading/trailing context around an excerpt body, split for independent styling. */
2
- export type ExcerptHtmlParts = {
3
- before: string;
4
- body: string;
5
- after: string;
1
+ export type NormalizeExcerptListOptions = {
2
+ /**
3
+ * When true, an unpaired closing `</ol>` is repaired by prepending an
4
+ * opening `<ol>` (preserving the ordered list). When false (the default,
5
+ * used for excerpt-only text with no known source order), it is converted
6
+ * to `</ul>` and an opening `<ul>` is prepended instead.
7
+ */
8
+ preserveOrderedLists?: boolean;
6
9
  };
7
- /**
8
- * Repairs the list fragments Ropewiki excerpts commonly produce, distributing
9
- * any added opening tags to `before` and closing tags to `after` so the three
10
- * parts remain a well-formed list when concatenated as `before + body + after`.
11
- */
12
- export declare function normalizeExcerptListHtmlParts(parts: ExcerptHtmlParts): ExcerptHtmlParts;
13
10
  /**
14
11
  * Repairs the list fragments Ropewiki excerpts commonly produce without
15
- * otherwise rewriting the source HTML.
12
+ * otherwise rewriting the source HTML, so the result is well-formed on its own.
16
13
  */
17
- export declare function normalizeExcerptListHtml(html: string): string;
14
+ export declare function normalizeExcerptListHtml(html: string, { preserveOrderedLists }?: NormalizeExcerptListOptions): string;
18
15
  //# sourceMappingURL=normalizeExcerptHtml.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalizeExcerptHtml.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/normalizeExcerptHtml.ts"],"names":[],"mappings":"AAIA,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAkCvF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ7D"}
1
+ {"version":3,"file":"normalizeExcerptHtml.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/normalizeExcerptHtml.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,2BAA2B,GAAG;IACtC;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,IAAI,EAAE,MAAM,EACZ,EAAE,oBAA4B,EAAE,GAAE,2BAAgC,GACnE,MAAM,CA+BR"}
@@ -1,59 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeExcerptListHtmlParts = normalizeExcerptListHtmlParts;
4
3
  exports.normalizeExcerptListHtml = normalizeExcerptListHtml;
5
4
  function countTags(html, pattern) {
6
5
  return html.match(pattern)?.length ?? 0;
7
6
  }
8
7
  /**
9
- * Repairs the list fragments Ropewiki excerpts commonly produce, distributing
10
- * any added opening tags to `before` and closing tags to `after` so the three
11
- * parts remain a well-formed list when concatenated as `before + body + after`.
8
+ * Repairs the list fragments Ropewiki excerpts commonly produce without
9
+ * otherwise rewriting the source HTML, so the result is well-formed on its own.
12
10
  */
13
- function normalizeExcerptListHtmlParts(parts) {
14
- let { before, body, after } = parts;
15
- const combined = () => before + body + after;
16
- const olOpenCount = countTags(combined(), /<ol\b[^>]*>/gi);
17
- const olCloseCount = countTags(combined(), /<\/ol\s*>/gi);
18
- if (olOpenCount === 0 && olCloseCount > 0) {
19
- before = before.replace(/<\/ol\s*>/gi, '</ul>');
20
- body = body.replace(/<\/ol\s*>/gi, '</ul>');
21
- after = after.replace(/<\/ol\s*>/gi, '</ul>');
22
- before = '<ul>'.repeat(olCloseCount) + before;
11
+ function normalizeExcerptListHtml(html, { preserveOrderedLists = false } = {}) {
12
+ if (html.length === 0)
13
+ return html;
14
+ let normalized = html;
15
+ const olOpenCount = countTags(normalized, /<ol\b[^>]*>/gi);
16
+ const olCloseCount = countTags(normalized, /<\/ol\s*>/gi);
17
+ if (!preserveOrderedLists && olOpenCount === 0 && olCloseCount > 0) {
18
+ normalized = normalized.replace(/<\/ol\s*>/gi, '</ul>');
19
+ normalized = '<ul>'.repeat(olCloseCount) + normalized;
23
20
  }
24
21
  else if (olOpenCount > olCloseCount) {
25
- after += '</ol>'.repeat(olOpenCount - olCloseCount);
22
+ normalized += '</ol>'.repeat(olOpenCount - olCloseCount);
26
23
  }
27
24
  else if (olCloseCount > olOpenCount) {
28
- before = '<ol>'.repeat(olCloseCount - olOpenCount) + before;
25
+ normalized = '<ol>'.repeat(olCloseCount - olOpenCount) + normalized;
29
26
  }
30
- const ulOpenCount = countTags(combined(), /<ul\b[^>]*>/gi);
31
- const ulCloseCount = countTags(combined(), /<\/ul\s*>/gi);
27
+ const ulOpenCount = countTags(normalized, /<ul\b[^>]*>/gi);
28
+ const ulCloseCount = countTags(normalized, /<\/ul\s*>/gi);
32
29
  if (ulOpenCount > ulCloseCount) {
33
- after += '</ul>'.repeat(ulOpenCount - ulCloseCount);
30
+ normalized += '</ul>'.repeat(ulOpenCount - ulCloseCount);
34
31
  }
35
32
  else if (ulCloseCount > ulOpenCount) {
36
- before = '<ul>'.repeat(ulCloseCount - ulOpenCount) + before;
33
+ normalized = '<ul>'.repeat(ulCloseCount - ulOpenCount) + normalized;
37
34
  }
38
- const hasListItem = /<li\b[^>]*>/i.test(combined());
39
- const hasListContainer = /<\/?(?:ul|ol)\b[^>]*>/i.test(combined());
35
+ const hasListItem = /<li\b[^>]*>/i.test(normalized);
36
+ const hasListContainer = /<\/?(?:ul|ol)\b[^>]*>/i.test(normalized);
40
37
  if (hasListItem && !hasListContainer) {
41
- before = `<ul>${before}`;
42
- after = `${after}</ul>`;
38
+ normalized = `<ul>${normalized}</ul>`;
43
39
  }
44
- return { before, body, after };
45
- }
46
- /**
47
- * Repairs the list fragments Ropewiki excerpts commonly produce without
48
- * otherwise rewriting the source HTML.
49
- */
50
- function normalizeExcerptListHtml(html) {
51
- if (html.length === 0)
52
- return html;
53
- const { before, body, after } = normalizeExcerptListHtmlParts({
54
- before: '',
55
- body: html,
56
- after: '',
57
- });
58
- return before + body + after;
40
+ return normalized;
59
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ropegeo-common",
3
- "version": "1.24.0",
3
+ "version": "1.24.1",
4
4
  "description": "Shared domain models and helpers for RopeGeo and WebScraper",
5
5
  "license": "ISC",
6
6
  "repository": {