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.
- package/dist/helpers/hydrateOfflinePageWirePaths.d.ts.map +1 -1
- package/dist/helpers/hydrateOfflinePageWirePaths.js +1 -0
- package/dist/models/betaSections/betaSection.d.ts +10 -1
- package/dist/models/betaSections/betaSection.d.ts.map +1 -1
- package/dist/models/betaSections/betaSection.js +56 -2
- package/dist/models/betaSections/betaSectionExcerpt.d.ts +2 -0
- package/dist/models/betaSections/betaSectionExcerpt.d.ts.map +1 -1
- package/dist/models/betaSections/betaSectionExcerpt.js +5 -0
- package/dist/models/betaSections/betaSectionImage.d.ts +4 -1
- package/dist/models/betaSections/betaSectionImage.d.ts.map +1 -1
- package/dist/models/betaSections/betaSectionImage.js +18 -1
- package/dist/models/betaSections/formatExcerptHtml.d.ts +16 -0
- package/dist/models/betaSections/formatExcerptHtml.d.ts.map +1 -0
- package/dist/models/betaSections/formatExcerptHtml.js +238 -0
- package/dist/models/betaSections/normalizeExcerptHtml.d.ts +6 -0
- package/dist/models/betaSections/normalizeExcerptHtml.d.ts.map +1 -0
- package/dist/models/betaSections/normalizeExcerptHtml.js +42 -0
- package/dist/models/betaSections/offlineBetaSection.d.ts +1 -1
- package/dist/models/betaSections/offlineBetaSection.d.ts.map +1 -1
- package/dist/models/betaSections/offlineBetaSection.js +2 -2
- package/dist/models/betaSections/offlineBetaSectionImage.d.ts +2 -1
- package/dist/models/betaSections/offlineBetaSectionImage.d.ts.map +1 -1
- package/dist/models/betaSections/offlineBetaSectionImage.js +6 -2
- package/dist/models/betaSections/onlineBetaSection.d.ts +2 -1
- package/dist/models/betaSections/onlineBetaSection.d.ts.map +1 -1
- package/dist/models/betaSections/onlineBetaSection.js +3 -3
- package/dist/models/betaSections/onlineBetaSectionImage.d.ts +3 -2
- package/dist/models/betaSections/onlineBetaSectionImage.d.ts.map +1 -1
- package/dist/models/betaSections/onlineBetaSectionImage.js +8 -4
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/minimap/abstract/pageMiniMap.d.ts +4 -1
- package/dist/models/minimap/abstract/pageMiniMap.d.ts.map +1 -1
- package/dist/models/minimap/abstract/pageMiniMap.js +18 -1
- package/dist/models/minimap/concrete/offlinePageMiniMap.d.ts +1 -1
- package/dist/models/minimap/concrete/offlinePageMiniMap.d.ts.map +1 -1
- package/dist/models/minimap/concrete/offlinePageMiniMap.js +3 -2
- package/dist/models/minimap/concrete/onlinePageMiniMap.d.ts +1 -1
- package/dist/models/minimap/concrete/onlinePageMiniMap.d.ts.map +1 -1
- package/dist/models/minimap/concrete/onlinePageMiniMap.js +3 -3
- package/dist/models/pageViews/offlinePageView.d.ts +10 -0
- package/dist/models/pageViews/offlinePageView.d.ts.map +1 -1
- package/dist/models/pageViews/offlineRopewikiPageView.d.ts +3 -2
- package/dist/models/pageViews/offlineRopewikiPageView.d.ts.map +1 -1
- package/dist/models/pageViews/offlineRopewikiPageView.js +18 -2
- package/dist/models/pageViews/onlinePageView.d.ts +10 -0
- package/dist/models/pageViews/onlinePageView.d.ts.map +1 -1
- package/dist/models/pageViews/onlineRopewikiPageView.d.ts +3 -2
- package/dist/models/pageViews/onlineRopewikiPageView.d.ts.map +1 -1
- package/dist/models/pageViews/onlineRopewikiPageView.js +23 -6
- package/dist/models/pageViews/ropewikiPageView.d.ts +7 -1
- package/dist/models/pageViews/ropewikiPageView.d.ts.map +1 -1
- package/dist/models/pageViews/ropewikiPageView.js +29 -1
- package/package.json +6 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrateOfflinePageWirePaths.d.ts","sourceRoot":"","sources":["../../src/helpers/hydrateOfflinePageWirePaths.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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":"
|
|
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
|
-
|
|
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;
|
|
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 @@
|
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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
|
}
|