ropegeo-common 1.21.0 → 1.23.0

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.
Files changed (54) hide show
  1. package/dist/helpers/hydrateOfflinePageWirePaths.d.ts.map +1 -1
  2. package/dist/helpers/hydrateOfflinePageWirePaths.js +1 -0
  3. package/dist/models/betaSections/betaSection.d.ts +10 -1
  4. package/dist/models/betaSections/betaSection.d.ts.map +1 -1
  5. package/dist/models/betaSections/betaSection.js +56 -2
  6. package/dist/models/betaSections/betaSectionExcerpt.d.ts +2 -0
  7. package/dist/models/betaSections/betaSectionExcerpt.d.ts.map +1 -1
  8. package/dist/models/betaSections/betaSectionExcerpt.js +5 -0
  9. package/dist/models/betaSections/betaSectionImage.d.ts +4 -1
  10. package/dist/models/betaSections/betaSectionImage.d.ts.map +1 -1
  11. package/dist/models/betaSections/betaSectionImage.js +18 -1
  12. package/dist/models/betaSections/formatExcerptHtml.d.ts +16 -0
  13. package/dist/models/betaSections/formatExcerptHtml.d.ts.map +1 -0
  14. package/dist/models/betaSections/formatExcerptHtml.js +238 -0
  15. package/dist/models/betaSections/normalizeExcerptHtml.d.ts +6 -0
  16. package/dist/models/betaSections/normalizeExcerptHtml.d.ts.map +1 -0
  17. package/dist/models/betaSections/normalizeExcerptHtml.js +42 -0
  18. package/dist/models/betaSections/offlineBetaSection.d.ts +1 -1
  19. package/dist/models/betaSections/offlineBetaSection.d.ts.map +1 -1
  20. package/dist/models/betaSections/offlineBetaSection.js +2 -2
  21. package/dist/models/betaSections/offlineBetaSectionImage.d.ts +2 -1
  22. package/dist/models/betaSections/offlineBetaSectionImage.d.ts.map +1 -1
  23. package/dist/models/betaSections/offlineBetaSectionImage.js +6 -2
  24. package/dist/models/betaSections/onlineBetaSection.d.ts +2 -1
  25. package/dist/models/betaSections/onlineBetaSection.d.ts.map +1 -1
  26. package/dist/models/betaSections/onlineBetaSection.js +3 -3
  27. package/dist/models/betaSections/onlineBetaSectionImage.d.ts +3 -2
  28. package/dist/models/betaSections/onlineBetaSectionImage.d.ts.map +1 -1
  29. package/dist/models/betaSections/onlineBetaSectionImage.js +8 -4
  30. package/dist/models/index.d.ts +2 -2
  31. package/dist/models/index.d.ts.map +1 -1
  32. package/dist/models/minimap/abstract/pageMiniMap.d.ts +4 -1
  33. package/dist/models/minimap/abstract/pageMiniMap.d.ts.map +1 -1
  34. package/dist/models/minimap/abstract/pageMiniMap.js +18 -1
  35. package/dist/models/minimap/concrete/offlinePageMiniMap.d.ts +1 -1
  36. package/dist/models/minimap/concrete/offlinePageMiniMap.d.ts.map +1 -1
  37. package/dist/models/minimap/concrete/offlinePageMiniMap.js +3 -2
  38. package/dist/models/minimap/concrete/onlinePageMiniMap.d.ts +1 -1
  39. package/dist/models/minimap/concrete/onlinePageMiniMap.d.ts.map +1 -1
  40. package/dist/models/minimap/concrete/onlinePageMiniMap.js +3 -3
  41. package/dist/models/pageViews/offlinePageView.d.ts +10 -0
  42. package/dist/models/pageViews/offlinePageView.d.ts.map +1 -1
  43. package/dist/models/pageViews/offlineRopewikiPageView.d.ts +3 -2
  44. package/dist/models/pageViews/offlineRopewikiPageView.d.ts.map +1 -1
  45. package/dist/models/pageViews/offlineRopewikiPageView.js +18 -2
  46. package/dist/models/pageViews/onlinePageView.d.ts +10 -0
  47. package/dist/models/pageViews/onlinePageView.d.ts.map +1 -1
  48. package/dist/models/pageViews/onlineRopewikiPageView.d.ts +3 -2
  49. package/dist/models/pageViews/onlineRopewikiPageView.d.ts.map +1 -1
  50. package/dist/models/pageViews/onlineRopewikiPageView.js +23 -6
  51. package/dist/models/pageViews/ropewikiPageView.d.ts +7 -1
  52. package/dist/models/pageViews/ropewikiPageView.d.ts.map +1 -1
  53. package/dist/models/pageViews/ropewikiPageView.js +29 -1
  54. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"file":"hydrateOfflinePageWirePaths.d.ts","sourceRoot":"","sources":["../../src/helpers/hydrateOfflinePageWirePaths.ts"],"names":[],"mappings":"AA6BA,qGAAqG;AACrG,wBAAgB,2BAA2B,CACvC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,IAAI,CAiBN"}
1
+ {"version":3,"file":"hydrateOfflinePageWirePaths.d.ts","sourceRoot":"","sources":["../../src/helpers/hydrateOfflinePageWirePaths.ts"],"names":[],"mappings":"AA8BA,qGAAqG;AACrG,wBAAgB,2BAA2B,CACvC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,IAAI,CAiBN"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.hydrateOfflinePageWirePaths = hydrateOfflinePageWirePaths;
4
4
  const offlinePageBundlePaths_1 = require("./offlinePageBundlePaths");
5
5
  const PATH_LIKE_KEYS = new Set([
6
+ 'downloadedPreviewPath',
6
7
  'downloadedBannerPath',
7
8
  'downloadedFullPath',
8
9
  'offlineTilesTemplate',
@@ -8,17 +8,26 @@ export declare abstract class BetaSection {
8
8
  title: string;
9
9
  text: string;
10
10
  latestRevisionDate: Date;
11
- protected constructor(order: number, title: string, text: string, latestRevisionDate: Date);
11
+ /** Stable database identity when supplied by the page-view API. */
12
+ id: string | null;
13
+ protected constructor(order: number, title: string, text: string, latestRevisionDate: Date, id?: string | null);
12
14
  /**
13
15
  * Builds a {@link BetaSectionExcerpt} for a verbatim excerpt from this section's text.
14
16
  * When the excerpt cannot be located, `start` and `end` are left undefined.
15
17
  */
16
18
  toExcerpt(excerptText: string, confidence: number): BetaSectionExcerpt;
19
+ /**
20
+ * Renders an excerpt with nearby section context when its offsets still
21
+ * match this section, otherwise falls back to the excerpt alone.
22
+ */
23
+ toExcerptHtml(excerpt: BetaSectionExcerpt): string;
17
24
  static fromResponseBody(body: unknown, fetchType?: FetchType): BetaSection;
18
25
  protected static assertNumber(obj: Record<string, unknown>, key: string): void;
19
26
  protected static assertString(obj: Record<string, unknown>, key: string): void;
20
27
  protected static assertIso8601DateString(obj: Record<string, unknown>, key: string): void;
21
28
  protected static normalizeCommonFields(obj: Record<string, unknown>, context: string, expectedFetchType: FetchType): void;
29
+ private static assertNullableNonEmptyString;
22
30
  protected static parseImagesArray(obj: Record<string, unknown>, key: string, expectedFetchType: FetchType): BetaSectionImage[];
31
+ protected static assertUniqueImageIds(images: BetaSectionImage[], sectionId: unknown): void;
23
32
  }
24
33
  //# sourceMappingURL=betaSection.d.ts.map
@@ -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;AAItD,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;IAEzB,SAAS,aACL,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,IAAI;IAQ5B;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,kBAAkB;IAWtE,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;IAaP,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;CAOxB"}
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;AAatD,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,MAAM;IAwClD,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"}
@@ -4,16 +4,19 @@ exports.BetaSection = void 0;
4
4
  exports.registerBetaSectionParser = registerBetaSectionParser;
5
5
  const betaSectionExcerpt_1 = require("./betaSectionExcerpt");
6
6
  const betaSectionImage_1 = require("./betaSectionImage");
7
+ const formatExcerptHtml_1 = require("./formatExcerptHtml");
8
+ const normalizeExcerptHtml_1 = require("./normalizeExcerptHtml");
7
9
  const betaSectionParsers = new Map();
8
10
  function registerBetaSectionParser(fetchType, parse) {
9
11
  betaSectionParsers.set(fetchType, parse);
10
12
  }
11
13
  class BetaSection {
12
- constructor(order, title, text, latestRevisionDate) {
14
+ constructor(order, title, text, latestRevisionDate, id = null) {
13
15
  this.order = order;
14
16
  this.title = title;
15
17
  this.text = text;
16
18
  this.latestRevisionDate = new Date(latestRevisionDate);
19
+ this.id = id;
17
20
  }
18
21
  /**
19
22
  * Builds a {@link BetaSectionExcerpt} for a verbatim excerpt from this section's text.
@@ -29,6 +32,32 @@ class BetaSection {
29
32
  }
30
33
  return new betaSectionExcerpt_1.BetaSectionExcerpt(excerptText, start, start + excerptText.length, confidence);
31
34
  }
35
+ /**
36
+ * Renders an excerpt with nearby section context when its offsets still
37
+ * match this section, otherwise falls back to the excerpt alone.
38
+ */
39
+ toExcerptHtml(excerpt) {
40
+ const validOffsets = excerpt.text != null &&
41
+ Number.isInteger(excerpt.start) &&
42
+ Number.isInteger(excerpt.end) &&
43
+ excerpt.start >= 0 &&
44
+ excerpt.end > excerpt.start &&
45
+ excerpt.end <= this.text.length &&
46
+ this.text.slice(excerpt.start, excerpt.end) === excerpt.text &&
47
+ (0, formatExcerptHtml_1.isSafeTextBoundary)(this.text, excerpt.start) &&
48
+ (0, formatExcerptHtml_1.isSafeTextBoundary)(this.text, excerpt.end);
49
+ if (!validOffsets)
50
+ return excerpt.toHtml();
51
+ const excerptStart = excerpt.start;
52
+ const excerptEnd = excerpt.end;
53
+ const characters = (0, formatExcerptHtml_1.visibleCharacters)(this.text);
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.normalizeExcerptListHtml)(`${(0, formatExcerptHtml_1.fadeText)(before, 'toward-end')}${excerpt.text}${(0, formatExcerptHtml_1.fadeText)(after, 'toward-start')}`);
60
+ }
32
61
  static fromResponseBody(body, fetchType) {
33
62
  if (body == null || typeof body !== 'object') {
34
63
  throw new Error('BetaSection body must be an object');
@@ -75,17 +104,42 @@ class BetaSection {
75
104
  BetaSection.assertString(obj, 'title');
76
105
  BetaSection.assertString(obj, 'text');
77
106
  BetaSection.assertIso8601DateString(obj, 'latestRevisionDate');
107
+ BetaSection.assertNullableNonEmptyString(obj, 'id');
78
108
  if (obj.fetchType !== expectedFetchType) {
79
109
  throw new Error(`${context}.fetchType must be "${expectedFetchType}", got: ${JSON.stringify(obj.fetchType)}`);
80
110
  }
81
111
  obj.latestRevisionDate = new Date(obj.latestRevisionDate);
82
112
  }
113
+ static assertNullableNonEmptyString(obj, key) {
114
+ const value = obj[key];
115
+ if (value === undefined || value === null) {
116
+ obj[key] = null;
117
+ return;
118
+ }
119
+ if (typeof value !== 'string' || value.trim().length === 0) {
120
+ throw new Error(`BetaSection.${key} must be a non-empty string or null, got: ${JSON.stringify(value)}`);
121
+ }
122
+ }
83
123
  static parseImagesArray(obj, key, expectedFetchType) {
84
124
  const v = obj[key];
85
125
  if (!Array.isArray(v)) {
86
126
  throw new Error(`BetaSection.${key} must be an array, got: ${typeof v}`);
87
127
  }
88
- return v.map((item) => betaSectionImage_1.BetaSectionImage.fromResult(item, expectedFetchType));
128
+ const images = v.map((item) => betaSectionImage_1.BetaSectionImage.fromResult(item, expectedFetchType));
129
+ BetaSection.assertUniqueImageIds(images, obj.id);
130
+ return images;
131
+ }
132
+ static assertUniqueImageIds(images, sectionId) {
133
+ const seen = new Set();
134
+ for (const image of images) {
135
+ if (seen.has(image.id)) {
136
+ const sectionLabel = typeof sectionId === 'string' && sectionId.length > 0
137
+ ? sectionId
138
+ : 'unknown';
139
+ throw new Error(`Duplicate image id ${image.id} in beta section ${sectionLabel}`);
140
+ }
141
+ seen.add(image.id);
142
+ }
89
143
  }
90
144
  }
91
145
  exports.BetaSection = BetaSection;
@@ -5,6 +5,8 @@ export declare class BetaSectionExcerpt {
5
5
  readonly confidence: number;
6
6
  constructor(text: string | undefined, start: number | undefined, end: number | undefined, confidence: number);
7
7
  toPlain(): Record<string, unknown>;
8
+ /** Returns displayable excerpt HTML when the owning beta section is unavailable. */
9
+ toHtml(): string;
8
10
  static fromResult(result: unknown): BetaSectionExcerpt;
9
11
  private static assertConfidence;
10
12
  private static assertNonNegativeInteger;
@@ -1 +1 @@
1
- {"version":3,"file":"betaSectionExcerpt.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/betaSectionExcerpt.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAkB;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAGxB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,UAAU,EAAE,MAAM;IAStB,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAclC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,kBAAkB;IAgCtD,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAQ/B,OAAO,CAAC,MAAM,CAAC,wBAAwB;CAK1C"}
1
+ {"version":3,"file":"betaSectionExcerpt.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/betaSectionExcerpt.ts"],"names":[],"mappings":"AAEA,qBAAa,kBAAkB;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAGxB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,UAAU,EAAE,MAAM;IAStB,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAclC,oFAAoF;IACpF,MAAM,IAAI,MAAM;IAIhB,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,kBAAkB;IAgCtD,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAQ/B,OAAO,CAAC,MAAM,CAAC,wBAAwB;CAK1C"}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BetaSectionExcerpt = void 0;
4
+ const normalizeExcerptHtml_1 = require("./normalizeExcerptHtml");
4
5
  class BetaSectionExcerpt {
5
6
  constructor(text, start, end, confidence) {
6
7
  BetaSectionExcerpt.assertConfidence(confidence);
@@ -22,6 +23,10 @@ class BetaSectionExcerpt {
22
23
  }
23
24
  return out;
24
25
  }
26
+ /** Returns displayable excerpt HTML when the owning beta section is unavailable. */
27
+ toHtml() {
28
+ return (0, normalizeExcerptHtml_1.normalizeExcerptListHtml)(this.text ?? '');
29
+ }
25
30
  static fromResult(result) {
26
31
  if (result == null || typeof result !== 'object' || Array.isArray(result)) {
27
32
  throw new Error('BetaSectionExcerpt result must be an object');
@@ -8,12 +8,15 @@ export declare abstract class BetaSectionImage {
8
8
  linkUrl: string;
9
9
  caption: string | null;
10
10
  latestRevisionDate: Date;
11
- protected constructor(order: number, id: string, linkUrl: string, caption: string | null, latestRevisionDate: Date);
11
+ /** File contributors; null when not resolved. */
12
+ authors: string[] | null;
13
+ protected constructor(order: number, id: string, linkUrl: string, caption: string | null, latestRevisionDate: Date, authors?: string[] | null);
12
14
  static fromResult(result: unknown, fetchType?: FetchType): BetaSectionImage;
13
15
  protected static assertNumber(obj: Record<string, unknown>, key: string): void;
14
16
  protected static assertNonEmptyString(obj: Record<string, unknown>, key: string): void;
15
17
  protected static assertString(obj: Record<string, unknown>, key: string): void;
16
18
  protected static assertStringOrNull(obj: Record<string, unknown>, key: string): void;
19
+ protected static assertNullableStringArray(obj: Record<string, unknown>, key: string): void;
17
20
  protected static assertDownloadBytesOrNull(obj: Record<string, unknown>, key: string): void;
18
21
  protected static assertIso8601DateString(obj: Record<string, unknown>, key: string): void;
19
22
  protected static normalizeCommonFields(obj: Record<string, unknown>, context: string, expectedFetchType: FetchType): void;
@@ -1 +1 @@
1
- {"version":3,"file":"betaSectionImage.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/betaSectionImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKzC,wBAAgB,8BAA8B,CAC1C,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,gBAAgB,GAC7C,IAAI,CAEN;AAED,8BAAsB,gBAAgB;IAClC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,kBAAkB,EAAE,IAAI,CAAC;IAEzB,SAAS,aACL,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,kBAAkB,EAAE,IAAI;IAS5B,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,gBAAgB;IA0B3E,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,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAStF,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,kBAAkB,CAC/B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,IAAI;IASP,SAAS,CAAC,MAAM,CAAC,yBAAyB,CACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,IAAI;IASP,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;CAcV"}
1
+ {"version":3,"file":"betaSectionImage.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/betaSectionImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKzC,wBAAgB,8BAA8B,CAC1C,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,gBAAgB,GAC7C,IAAI,CAEN;AAED,8BAAsB,gBAAgB;IAClC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,kBAAkB,EAAE,IAAI,CAAC;IACzB,iDAAiD;IACjD,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEzB,SAAS,aACL,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,kBAAkB,EAAE,IAAI,EACxB,OAAO,GAAE,MAAM,EAAE,GAAG,IAAW;IAUnC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,gBAAgB;IA0B3E,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,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAStF,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,kBAAkB,CAC/B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,IAAI;IASP,SAAS,CAAC,MAAM,CAAC,yBAAyB,CACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,IAAI;IAkBP,SAAS,CAAC,MAAM,CAAC,yBAAyB,CACtC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,GAAG,EAAE,MAAM,GACZ,IAAI;IASP,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;CAeV"}
@@ -8,12 +8,13 @@ function registerBetaSectionImageParser(fetchType, parse) {
8
8
  betaSectionImageParsers.set(fetchType, parse);
9
9
  }
10
10
  class BetaSectionImage {
11
- constructor(order, id, linkUrl, caption, latestRevisionDate) {
11
+ constructor(order, id, linkUrl, caption, latestRevisionDate, authors = null) {
12
12
  this.order = order;
13
13
  this.id = id;
14
14
  this.linkUrl = linkUrl;
15
15
  this.caption = caption;
16
16
  this.latestRevisionDate = new Date(latestRevisionDate);
17
+ this.authors = authors != null ? authors.slice() : null;
17
18
  }
18
19
  static fromResult(result, fetchType) {
19
20
  if (result == null || typeof result !== 'object') {
@@ -58,6 +59,21 @@ class BetaSectionImage {
58
59
  throw new Error(`BetaSectionImage.${key} must be a string or null, got: ${typeof v}`);
59
60
  }
60
61
  }
62
+ static assertNullableStringArray(obj, key) {
63
+ const v = obj[key];
64
+ if (v === undefined || v === null) {
65
+ obj[key] = null;
66
+ return;
67
+ }
68
+ if (!Array.isArray(v)) {
69
+ throw new Error(`BetaSectionImage.${key} must be an array of strings or null, got: ${typeof v}`);
70
+ }
71
+ for (let i = 0; i < v.length; i++) {
72
+ if (typeof v[i] !== 'string') {
73
+ throw new Error(`BetaSectionImage.${key}[${i}] must be a string`);
74
+ }
75
+ }
76
+ }
61
77
  static assertDownloadBytesOrNull(obj, key) {
62
78
  const v = obj[key];
63
79
  if (v === undefined || v === null) {
@@ -82,6 +98,7 @@ class BetaSectionImage {
82
98
  BetaSectionImage.assertString(obj, 'linkUrl');
83
99
  BetaSectionImage.assertStringOrNull(obj, 'caption');
84
100
  BetaSectionImage.assertIso8601DateString(obj, 'latestRevisionDate');
101
+ BetaSectionImage.assertNullableStringArray(obj, 'authors');
85
102
  const rawFetchType = obj.fetchType;
86
103
  if (rawFetchType !== expectedFetchType) {
87
104
  throw new Error(`${context}.fetchType must be "${expectedFetchType}", got: ${JSON.stringify(rawFetchType)}`);
@@ -0,0 +1,16 @@
1
+ type VisibleCharacter = {
2
+ value: string;
3
+ index: number;
4
+ };
5
+ export declare function visibleCharacters(html: string): VisibleCharacter[];
6
+ /** True when an offset does not split tag markup or a character entity. */
7
+ export declare function isSafeTextBoundary(html: string, offset: number): boolean;
8
+ export declare function previousSentenceStart(html: string, anchor: number, characters: VisibleCharacter[]): number;
9
+ export declare function nextSentenceEnd(html: string, anchor: number, characters: VisibleCharacter[]): number;
10
+ export declare function listContext(html: string, excerptStart: number, excerptEnd: number, characters: VisibleCharacter[]): {
11
+ before: string;
12
+ after: string;
13
+ } | null;
14
+ export declare function fadeText(html: string, direction: 'toward-end' | 'toward-start'): string;
15
+ export {};
16
+ //# sourceMappingURL=formatExcerptHtml.d.ts.map
@@ -0,0 +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;AAED,wBAAgB,QAAQ,CACpB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,YAAY,GAAG,cAAc,GACzC,MAAM,CA8BR"}
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.visibleCharacters = visibleCharacters;
4
+ exports.isSafeTextBoundary = isSafeTextBoundary;
5
+ exports.previousSentenceStart = previousSentenceStart;
6
+ exports.nextSentenceEnd = nextSentenceEnd;
7
+ exports.listContext = listContext;
8
+ exports.fadeText = fadeText;
9
+ const HEADER_TAG = /^<\s*(\/?)h[1-6]\b/i;
10
+ const NON_TERMINAL_ABBREVIATIONS = new Set([
11
+ 'dr',
12
+ 'e.g',
13
+ 'etc',
14
+ 'i.e',
15
+ 'mr',
16
+ 'mrs',
17
+ 'ms',
18
+ 'no',
19
+ 'st',
20
+ 'vs',
21
+ ]);
22
+ function visibleCharacters(html) {
23
+ const characters = [];
24
+ let headerDepth = 0;
25
+ for (let index = 0; index < html.length; index += 1) {
26
+ if (html[index] === '<') {
27
+ const end = html.indexOf('>', index + 1);
28
+ if (end === -1)
29
+ break;
30
+ const tag = html.slice(index, end + 1);
31
+ const header = tag.match(HEADER_TAG);
32
+ if (header != null) {
33
+ headerDepth += header[1] === '/' ? -1 : 1;
34
+ headerDepth = Math.max(0, headerDepth);
35
+ }
36
+ index = end;
37
+ continue;
38
+ }
39
+ if (headerDepth === 0) {
40
+ characters.push({ value: html[index], index });
41
+ }
42
+ }
43
+ return characters;
44
+ }
45
+ function hasVisibleText(characters, start, end) {
46
+ return characters.some(({ value, index }) => index >= start && index < end && /\S/.test(value));
47
+ }
48
+ /** True when an offset does not split tag markup or a character entity. */
49
+ function isSafeTextBoundary(html, offset) {
50
+ if (offset < 0 || offset > html.length)
51
+ return false;
52
+ if (offset > 0 && offset < html.length) {
53
+ const code = html.charCodeAt(offset - 1);
54
+ if (code >= 0xd800 && code <= 0xdbff)
55
+ return false;
56
+ }
57
+ let inTag = false;
58
+ let quote = null;
59
+ for (let index = 0; index < offset; index += 1) {
60
+ const char = html[index];
61
+ if (inTag) {
62
+ if (quote != null) {
63
+ if (char === quote)
64
+ quote = null;
65
+ continue;
66
+ }
67
+ if (char === '"' || char === "'") {
68
+ quote = char;
69
+ continue;
70
+ }
71
+ if (char === '>')
72
+ inTag = false;
73
+ continue;
74
+ }
75
+ if (char === '<')
76
+ inTag = true;
77
+ }
78
+ if (inTag)
79
+ return false;
80
+ const entityStart = html.lastIndexOf('&', Math.max(0, offset - 1));
81
+ if (entityStart !== -1 && entityStart < offset) {
82
+ const semicolon = html.indexOf(';', entityStart);
83
+ if (semicolon >= offset) {
84
+ const entity = html.slice(entityStart, semicolon + 1);
85
+ if (/^&(?:#\d{1,7}|#x[\da-f]{1,6}|[a-z][\w:-]{0,31});$/i.test(entity)) {
86
+ return false;
87
+ }
88
+ }
89
+ }
90
+ return true;
91
+ }
92
+ function isSentenceBoundary(characters, characterIndex) {
93
+ const current = characters[characterIndex];
94
+ if (current.value === '!' || current.value === '?')
95
+ return true;
96
+ if (current.value !== '.')
97
+ return false;
98
+ const previous = characters[characterIndex - 1]?.value;
99
+ const next = characters[characterIndex + 1]?.value;
100
+ if (previous != null && next != null && /\d/.test(previous) && /\d/.test(next)) {
101
+ return false;
102
+ }
103
+ if (previous != null &&
104
+ next != null &&
105
+ /[A-Za-z]/.test(previous) &&
106
+ /[A-Za-z]/.test(next) &&
107
+ characters[characterIndex + 2]?.value === '.') {
108
+ return false;
109
+ }
110
+ let token = '';
111
+ for (let index = characterIndex - 1; index >= 0; index -= 1) {
112
+ const value = characters[index].value;
113
+ if (!/[A-Za-z.]/.test(value))
114
+ break;
115
+ token = value + token;
116
+ }
117
+ const normalized = token.toLowerCase();
118
+ return (token.length > 1 &&
119
+ !NON_TERMINAL_ABBREVIATIONS.has(normalized) &&
120
+ !/^(?:[a-z]\.)+[a-z]?$/i.test(token));
121
+ }
122
+ function previousSentenceStart(html, anchor, characters) {
123
+ const boundaries = characters
124
+ .map((character, index) => ({ character, index }))
125
+ .filter(({ character, index }) => character.index < anchor &&
126
+ isSentenceBoundary(characters, index))
127
+ .map(({ character }) => character.index + 1);
128
+ if (boundaries.length === 0)
129
+ return 0;
130
+ const latest = boundaries[boundaries.length - 1];
131
+ const boundary = hasVisibleText(characters, latest, anchor)
132
+ ? latest
133
+ : boundaries[boundaries.length - 2];
134
+ let start = boundary ?? 0;
135
+ while (start < anchor && /\s/.test(html[start]))
136
+ start += 1;
137
+ return start;
138
+ }
139
+ function nextSentenceEnd(html, anchor, characters) {
140
+ const boundary = characters.find(({ index }, characterIndex) => index >= anchor && isSentenceBoundary(characters, characterIndex));
141
+ if (boundary == null)
142
+ return html.length;
143
+ let end = boundary.index + 1;
144
+ while (end < html.length) {
145
+ const rest = html.slice(end);
146
+ const whitespace = rest.match(/^\s+/);
147
+ if (whitespace != null) {
148
+ end += whitespace[0].length;
149
+ continue;
150
+ }
151
+ const closingTag = rest.match(/^<\/(?!ul\b|ol\b|li\b)[a-z][^>]*>/i);
152
+ if (closingTag != null) {
153
+ end += closingTag[0].length;
154
+ continue;
155
+ }
156
+ break;
157
+ }
158
+ return end;
159
+ }
160
+ function findContainingList(html, excerptStart, excerptEnd) {
161
+ const stack = [];
162
+ const ranges = [];
163
+ const tags = html.matchAll(/<(\/?)(ul|ol)\b[^>]*>/gi);
164
+ for (const tag of tags) {
165
+ const start = tag.index;
166
+ const end = start + tag[0].length;
167
+ const name = tag[2].toLowerCase();
168
+ if (tag[1] !== '/') {
169
+ stack.push({ name, start, end });
170
+ continue;
171
+ }
172
+ const openIndex = stack.map(({ name: n }) => n).lastIndexOf(name);
173
+ if (openIndex === -1)
174
+ continue;
175
+ const [open] = stack.splice(openIndex, 1);
176
+ if (open.start <= excerptStart && end >= excerptEnd) {
177
+ ranges.push({
178
+ openStart: open.start,
179
+ openEnd: open.end,
180
+ closeStart: start,
181
+ closeEnd: end,
182
+ });
183
+ }
184
+ }
185
+ return (ranges.sort((a, b) => b.openStart - a.openStart || a.closeEnd - b.closeEnd)[0] ?? null);
186
+ }
187
+ function listContext(html, excerptStart, excerptEnd, characters) {
188
+ const excerpt = html.slice(excerptStart, excerptEnd).trim();
189
+ if (!/^<li\b/i.test(excerpt) || !/<\/li\s*>$/i.test(excerpt)) {
190
+ return null;
191
+ }
192
+ const range = findContainingList(html, excerptStart, excerptEnd);
193
+ if (range == null)
194
+ return null;
195
+ const listBody = html.slice(range.openEnd, range.closeStart);
196
+ const items = [...listBody.matchAll(/<li\b[^>]*>[\s\S]*?<\/li\s*>/gi)].map((match) => ({
197
+ start: range.openEnd + match.index,
198
+ end: range.openEnd + match.index + match[0].length,
199
+ }));
200
+ const previous = items.filter(({ end }) => end <= excerptStart).at(-1);
201
+ const next = items.find(({ start }) => start >= excerptEnd);
202
+ const before = previous != null
203
+ ? html.slice(range.openStart, range.openEnd) +
204
+ html.slice(previous.start, previous.end)
205
+ : html.slice(previousSentenceStart(html, range.openStart, characters), range.openEnd);
206
+ const after = next != null
207
+ ? html.slice(next.start, next.end) +
208
+ html.slice(range.closeStart, range.closeEnd)
209
+ : html.slice(range.closeStart, nextSentenceEnd(html, range.closeEnd, characters));
210
+ return { before, after };
211
+ }
212
+ function fadeText(html, direction) {
213
+ const parts = html.split(/(<[^>]+>)/g);
214
+ const wordCount = parts.reduce((count, part) => part.startsWith('<')
215
+ ? count
216
+ : count + (part.match(/\S+/g)?.length ?? 0), 0);
217
+ if (wordCount === 0)
218
+ return html;
219
+ let wordIndex = 0;
220
+ return parts
221
+ .map((part) => {
222
+ if (part.startsWith('<'))
223
+ return part;
224
+ return part
225
+ .split(/(\s+)/)
226
+ .map((token) => {
227
+ if (token.length === 0 || /^\s+$/.test(token))
228
+ return token;
229
+ const progress = wordCount === 1 ? 1 : wordIndex / (wordCount - 1);
230
+ const proximity = direction === 'toward-end' ? progress : 1 - progress;
231
+ const opacity = (0.3 + proximity * 0.4).toFixed(2);
232
+ wordIndex += 1;
233
+ return `<span style="opacity: ${opacity}">${token}</span>`;
234
+ })
235
+ .join('');
236
+ })
237
+ .join('');
238
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Repairs the list fragments Ropewiki excerpts commonly produce without
3
+ * otherwise rewriting the source HTML.
4
+ */
5
+ export declare function normalizeExcerptListHtml(html: string): string;
6
+ //# sourceMappingURL=normalizeExcerptHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeExcerptHtml.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/normalizeExcerptHtml.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgC7D"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeExcerptListHtml = normalizeExcerptListHtml;
4
+ function countTags(html, pattern) {
5
+ return html.match(pattern)?.length ?? 0;
6
+ }
7
+ /**
8
+ * Repairs the list fragments Ropewiki excerpts commonly produce without
9
+ * otherwise rewriting the source HTML.
10
+ */
11
+ function normalizeExcerptListHtml(html) {
12
+ if (html.length === 0)
13
+ return html;
14
+ let normalized = html;
15
+ let olOpenCount = countTags(normalized, /<ol\b[^>]*>/gi);
16
+ let olCloseCount = countTags(normalized, /<\/ol\s*>/gi);
17
+ if (olOpenCount === 0 && olCloseCount > 0) {
18
+ normalized = normalized.replace(/<\/ol\s*>/gi, '</ul>');
19
+ normalized = '<ul>'.repeat(olCloseCount) + normalized;
20
+ olCloseCount = 0;
21
+ }
22
+ else if (olOpenCount > olCloseCount) {
23
+ normalized += '</ol>'.repeat(olOpenCount - olCloseCount);
24
+ }
25
+ else if (olCloseCount > olOpenCount) {
26
+ normalized = '<ol>'.repeat(olCloseCount - olOpenCount) + normalized;
27
+ }
28
+ const ulOpenCount = countTags(normalized, /<ul\b[^>]*>/gi);
29
+ const ulCloseCount = countTags(normalized, /<\/ul\s*>/gi);
30
+ if (ulOpenCount > ulCloseCount) {
31
+ normalized += '</ul>'.repeat(ulOpenCount - ulCloseCount);
32
+ }
33
+ else if (ulCloseCount > ulOpenCount) {
34
+ normalized = '<ul>'.repeat(ulCloseCount - ulOpenCount) + normalized;
35
+ }
36
+ const hasListItem = /<li\b[^>]*>/i.test(normalized);
37
+ const hasListContainer = /<\/?(?:ul|ol)\b[^>]*>/i.test(normalized);
38
+ if (hasListItem && !hasListContainer) {
39
+ normalized = `<ul>${normalized}</ul>`;
40
+ }
41
+ return normalized;
42
+ }
@@ -3,7 +3,7 @@ import { OfflineBetaSectionImage } from './offlineBetaSectionImage';
3
3
  export declare class OfflineBetaSection extends BetaSection {
4
4
  readonly fetchType: "offline";
5
5
  images: OfflineBetaSectionImage[];
6
- constructor(order: number, title: string, text: string, latestRevisionDate: Date, images?: OfflineBetaSectionImage[]);
6
+ constructor(order: number, title: string, text: string, latestRevisionDate: Date, images?: OfflineBetaSectionImage[], id?: string | null);
7
7
  static fromResponseBody(body: unknown): OfflineBetaSection;
8
8
  }
9
9
  //# sourceMappingURL=offlineBetaSection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"offlineBetaSection.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/offlineBetaSection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA6B,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,qBAAa,kBAAmB,SAAQ,WAAW;IAC/C,QAAQ,CAAC,SAAS,EAAG,SAAS,CAAU;IACxC,MAAM,EAAE,uBAAuB,EAAE,CAAC;gBAG9B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,IAAI,EACxB,MAAM,CAAC,EAAE,uBAAuB,EAAE;IAMtC,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB;CAU7D"}
1
+ {"version":3,"file":"offlineBetaSection.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/offlineBetaSection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA6B,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE,qBAAa,kBAAmB,SAAQ,WAAW;IAC/C,QAAQ,CAAC,SAAS,EAAG,SAAS,CAAU;IACxC,MAAM,EAAE,uBAAuB,EAAE,CAAC;gBAG9B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,kBAAkB,EAAE,IAAI,EACxB,MAAM,CAAC,EAAE,uBAAuB,EAAE,EAClC,EAAE,GAAE,MAAM,GAAG,IAAW;IAM5B,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB;CAU7D"}
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OfflineBetaSection = void 0;
4
4
  const betaSection_1 = require("./betaSection");
5
5
  class OfflineBetaSection extends betaSection_1.BetaSection {
6
- constructor(order, title, text, latestRevisionDate, images) {
7
- super(order, title, text, latestRevisionDate);
6
+ constructor(order, title, text, latestRevisionDate, images, id = null) {
7
+ super(order, title, text, latestRevisionDate, id);
8
8
  this.fetchType = 'offline';
9
9
  this.images = Array.isArray(images) ? images : [];
10
10
  }
@@ -3,7 +3,8 @@ export declare class OfflineBetaSectionImage extends BetaSectionImage {
3
3
  readonly fetchType: "offline";
4
4
  downloadedBannerPath: string | null;
5
5
  downloadedFullPath: string | null;
6
- constructor(order: number, id: string, downloadedBannerPath: string | null, downloadedFullPath: string | null, linkUrl: string, caption: string | null, latestRevisionDate: Date);
6
+ downloadedPreviewPath: string | null;
7
+ constructor(order: number, id: string, downloadedBannerPath: string | null, downloadedFullPath: string | null, linkUrl: string, caption: string | null, latestRevisionDate: Date, downloadedPreviewPath?: string | null, authors?: string[] | null);
7
8
  static fromResult(result: unknown): OfflineBetaSectionImage;
8
9
  }
9
10
  //# sourceMappingURL=offlineBetaSectionImage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"offlineBetaSectionImage.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/offlineBetaSectionImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAkC,MAAM,oBAAoB,CAAC;AAEtF,qBAAa,uBAAwB,SAAQ,gBAAgB;IACzD,QAAQ,CAAC,SAAS,EAAG,SAAS,CAAU;IACxC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;gBAG9B,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,oBAAoB,EAAE,MAAM,GAAG,IAAI,EACnC,kBAAkB,EAAE,MAAM,GAAG,IAAI,EACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,kBAAkB,EAAE,IAAI;IAO5B,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,uBAAuB;CAc9D"}
1
+ {"version":3,"file":"offlineBetaSectionImage.d.ts","sourceRoot":"","sources":["../../../src/models/betaSections/offlineBetaSectionImage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAkC,MAAM,oBAAoB,CAAC;AAEtF,qBAAa,uBAAwB,SAAQ,gBAAgB;IACzD,QAAQ,CAAC,SAAS,EAAG,SAAS,CAAU;IACxC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;gBAGjC,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,oBAAoB,EAAE,MAAM,GAAG,IAAI,EACnC,kBAAkB,EAAE,MAAM,GAAG,IAAI,EACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,kBAAkB,EAAE,IAAI,EACxB,qBAAqB,GAAE,MAAM,GAAG,IAAW,EAC3C,OAAO,GAAE,MAAM,EAAE,GAAG,IAAW;IAQnC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,GAAG,uBAAuB;CAgB9D"}
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OfflineBetaSectionImage = void 0;
4
4
  const betaSectionImage_1 = require("./betaSectionImage");
5
5
  class OfflineBetaSectionImage extends betaSectionImage_1.BetaSectionImage {
6
- constructor(order, id, downloadedBannerPath, downloadedFullPath, linkUrl, caption, latestRevisionDate) {
7
- super(order, id, linkUrl, caption, latestRevisionDate);
6
+ constructor(order, id, downloadedBannerPath, downloadedFullPath, linkUrl, caption, latestRevisionDate, downloadedPreviewPath = null, authors = null) {
7
+ super(order, id, linkUrl, caption, latestRevisionDate, authors);
8
8
  this.fetchType = 'offline';
9
9
  this.downloadedBannerPath = downloadedBannerPath;
10
10
  this.downloadedFullPath = downloadedFullPath;
11
+ this.downloadedPreviewPath = downloadedPreviewPath;
11
12
  }
12
13
  static fromResult(result) {
13
14
  if (result == null || typeof result !== 'object') {
@@ -17,6 +18,9 @@ class OfflineBetaSectionImage extends betaSectionImage_1.BetaSectionImage {
17
18
  betaSectionImage_1.BetaSectionImage.normalizeCommonFields(r, 'OfflineBetaSectionImage', 'offline');
18
19
  betaSectionImage_1.BetaSectionImage.assertStringOrNull(r, 'downloadedBannerPath');
19
20
  betaSectionImage_1.BetaSectionImage.assertStringOrNull(r, 'downloadedFullPath');
21
+ if (r.downloadedPreviewPath === undefined)
22
+ r.downloadedPreviewPath = null;
23
+ betaSectionImage_1.BetaSectionImage.assertStringOrNull(r, 'downloadedPreviewPath');
20
24
  if ('downloadBytes' in r && r.downloadBytes != null) {
21
25
  throw new Error('OfflineBetaSectionImage.downloadBytes is not supported');
22
26
  }