@mark1russell7/site-pages 0.1.3 → 0.1.5
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.
|
@@ -69,7 +69,7 @@ function versionOfTag(tag) {
|
|
|
69
69
|
const VERSION_HEADING = /^##\s+\[?v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\]?(?:\s.*)?$/;
|
|
70
70
|
const COMMIT_ENTRY = /^([0-9a-f]{7,40}):\s+([\s\S]*)$/;
|
|
71
71
|
const DEPENDENCY_ENTRY = /^(?:Updated dependencies\b|@?[\w.-]+(?:\/[\w.-]+)?@\S+\s*$)/;
|
|
72
|
-
const GENERATED_NOTE = /\
|
|
72
|
+
const GENERATED_NOTE = /\sby\s+@[\w-]+(?:\[bot\])?\s+in\s+(https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/(\d+))\s*$/;
|
|
73
73
|
const FIRST_CONTRIBUTION = /\bmade their first contribution\b/;
|
|
74
74
|
/**
|
|
75
75
|
* This function gives the first sentence of a text. A full stop, a question mark or an exclamation
|
|
@@ -108,7 +108,8 @@ function entryOf(lines) {
|
|
|
108
108
|
const commit = COMMIT_ENTRY.exec(text);
|
|
109
109
|
let body = (commit ? commit[2] : text) ?? "";
|
|
110
110
|
if (body.trim() === "" || DEPENDENCY_ENTRY.test(body.trim()) || FIRST_CONTRIBUTION.test(body)) return null;
|
|
111
|
-
|
|
111
|
+
const note = GENERATED_NOTE.exec(body);
|
|
112
|
+
if (note) body = `${body.slice(0, note.index).trimEnd()} ([#${note[2] ?? ""}](${note[1] ?? ""}))`;
|
|
112
113
|
return {
|
|
113
114
|
commit: commit?.[1] ?? null,
|
|
114
115
|
text: firstSentence(body)
|
|
@@ -179,10 +180,10 @@ function releaseBodyEntries(body) {
|
|
|
179
180
|
continue;
|
|
180
181
|
}
|
|
181
182
|
if (fence !== null) continue;
|
|
182
|
-
const item = /^[-*+]\s+(.*)
|
|
183
|
-
if (item
|
|
183
|
+
const item = /^[-*+]\s+(\S.*)?$/.exec(line);
|
|
184
|
+
if (item) {
|
|
184
185
|
close();
|
|
185
|
-
entry = [item[1]];
|
|
186
|
+
entry = [item[1] ?? ""];
|
|
186
187
|
} else if (entry && (line.trim() === "" || /^\s/.test(line))) entry.push(line);
|
|
187
188
|
else close();
|
|
188
189
|
}
|
|
@@ -475,7 +476,7 @@ async function npmPublishTimes(options) {
|
|
|
475
476
|
const queue = [...names].sort();
|
|
476
477
|
const worker = async () => {
|
|
477
478
|
for (let name = queue.shift(); name !== void 0; name = queue.shift()) try {
|
|
478
|
-
const packument = object(await get(`${registry}/${name.
|
|
479
|
+
const packument = object(await get(`${registry}/${name.replaceAll("/", "%2f")}`));
|
|
479
480
|
const published = object(packument["versions"]);
|
|
480
481
|
for (const [version, date] of Object.entries(object(packument["time"]))) {
|
|
481
482
|
if (version === "created" || version === "modified" || typeof date !== "string") continue;
|
|
@@ -986,13 +987,78 @@ function splitRow(line) {
|
|
|
986
987
|
cells.push(cell.trim());
|
|
987
988
|
return cells;
|
|
988
989
|
}
|
|
990
|
+
/** A whitespace character, as `\s` of a regular expression and `String.prototype.trim` read it. */
|
|
991
|
+
const SPACE = /\s/;
|
|
992
|
+
/** A character that `.` of a regular expression does not match. */
|
|
993
|
+
const LINE_TERMINATOR = /[\n\r\u2028\u2029]/;
|
|
994
|
+
/** The first whitespace character at or after `lastIndex`. */
|
|
995
|
+
const NEXT_SPACE = /\s/g;
|
|
996
|
+
/**
|
|
997
|
+
* This function replaces each Markdown link `[label](target)` of a text with the result of
|
|
998
|
+
* `replace`. The label ends at the first `]`, and the target ends at the first `)`. In `strict`
|
|
999
|
+
* mode, the label and the target are not empty, and the target has no whitespace.
|
|
1000
|
+
*
|
|
1001
|
+
* @remarks The function reads the text one time. A regular expression for these links can
|
|
1002
|
+
* backtrack (CodeQL `js/polynomial-redos`). For a long text with many `[` characters, its time
|
|
1003
|
+
* grows with the square of the length.
|
|
1004
|
+
*/
|
|
1005
|
+
function replaceLinks(text, strict, replace) {
|
|
1006
|
+
let result = "";
|
|
1007
|
+
let copied = 0;
|
|
1008
|
+
let close = -1;
|
|
1009
|
+
let end = -1;
|
|
1010
|
+
let space = -1;
|
|
1011
|
+
for (let open = text.indexOf("["); open !== -1; open = text.indexOf("[", open + 1)) {
|
|
1012
|
+
if (close <= open) close = text.indexOf("]", open + 1);
|
|
1013
|
+
if (close === -1) break;
|
|
1014
|
+
if (text[close + 1] !== "(" || strict && close === open + 1) continue;
|
|
1015
|
+
const start = close + 2;
|
|
1016
|
+
if (end < start) end = text.indexOf(")", start);
|
|
1017
|
+
if (end === -1) break;
|
|
1018
|
+
if (strict) {
|
|
1019
|
+
if (space !== Infinity && space < start) {
|
|
1020
|
+
NEXT_SPACE.lastIndex = start;
|
|
1021
|
+
space = NEXT_SPACE.exec(text)?.index ?? Infinity;
|
|
1022
|
+
}
|
|
1023
|
+
if (end === start || space < end) continue;
|
|
1024
|
+
}
|
|
1025
|
+
result += text.slice(copied, open) + replace(text.slice(open + 1, close), text.slice(start, end));
|
|
1026
|
+
copied = end + 1;
|
|
1027
|
+
open = end;
|
|
1028
|
+
}
|
|
1029
|
+
return result + text.slice(copied);
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* This function gives the text of an ATX heading without its closing sequence: the whitespace,
|
|
1033
|
+
* then the `#` characters, then the whitespace at the end. It gives null when the rest has a line
|
|
1034
|
+
* terminator, as the regular expression `(.*?)\s*#*\s*$` does.
|
|
1035
|
+
*/
|
|
1036
|
+
function closeHeading(text) {
|
|
1037
|
+
let end = text.length;
|
|
1038
|
+
while (end > 0 && SPACE.test(text[end - 1] ?? "")) end--;
|
|
1039
|
+
while (end > 0 && text[end - 1] === "#") end--;
|
|
1040
|
+
while (end > 0 && SPACE.test(text[end - 1] ?? "")) end--;
|
|
1041
|
+
const rest = text.slice(0, end);
|
|
1042
|
+
return LINE_TERMINATOR.test(rest) ? null : rest;
|
|
1043
|
+
}
|
|
1044
|
+
/** The start of an ATX heading. The text after the whitespace goes to `closeHeading`. */
|
|
1045
|
+
const HEADING = /^(#{1,6})\s+(\S[\s\S]*)?$/;
|
|
1046
|
+
/** This function gives the level and the text of an ATX heading line, or null for another line. */
|
|
1047
|
+
function headingOf(line) {
|
|
1048
|
+
const match = HEADING.exec(line);
|
|
1049
|
+
const text = match ? closeHeading(match[2] ?? "") : null;
|
|
1050
|
+
return match && text !== null ? {
|
|
1051
|
+
level: match[1]?.length ?? 0,
|
|
1052
|
+
text
|
|
1053
|
+
} : null;
|
|
1054
|
+
}
|
|
989
1055
|
/** This function gives the plain text of inline Markdown: no code marks, no emphasis, the text of each link. */
|
|
990
1056
|
function plainText(markdown) {
|
|
991
|
-
return String(markdown ?? "")
|
|
1057
|
+
return replaceLinks(String(markdown ?? ""), false, (label) => label).replace(/[`*_]/g, "").replace(/\s+/g, " ").trim();
|
|
992
1058
|
}
|
|
993
1059
|
/** A cell with no value: empty, or a dash. */
|
|
994
1060
|
function isEmpty(cell) {
|
|
995
|
-
return cell === void 0 ||
|
|
1061
|
+
return cell === void 0 || /^(?:[-–—]|n\/a)?$/i.test(cell.trim());
|
|
996
1062
|
}
|
|
997
1063
|
/**
|
|
998
1064
|
* This function gives the tables of a Markdown file. Each table has its header names in lowercase
|
|
@@ -1012,14 +1078,14 @@ function markdownTables(markdown) {
|
|
|
1012
1078
|
continue;
|
|
1013
1079
|
}
|
|
1014
1080
|
if (fence !== null) continue;
|
|
1015
|
-
const heading =
|
|
1081
|
+
const heading = headingOf(line);
|
|
1016
1082
|
if (heading) {
|
|
1017
|
-
section = plainText(heading
|
|
1083
|
+
section = plainText(heading.text);
|
|
1018
1084
|
continue;
|
|
1019
1085
|
}
|
|
1020
1086
|
if (!line.trim().startsWith("|")) continue;
|
|
1021
1087
|
const separator = lines[index + 1] ?? "";
|
|
1022
|
-
if (!/^\s
|
|
1088
|
+
if (!/^\s*(?:\|\s*)?:?-{3,}/.test(separator)) continue;
|
|
1023
1089
|
const header = splitRow(line).map((name) => plainText(name).toLowerCase());
|
|
1024
1090
|
const rows = [];
|
|
1025
1091
|
let next = index + 2;
|
|
@@ -1059,6 +1125,20 @@ function linkUrl(target, { file = "", repository = "", ref = "HEAD" }) {
|
|
|
1059
1125
|
return `https://github.com/${repository}/blob/${ref}/${resolved}${anchor ? `#${anchor}` : ""}`;
|
|
1060
1126
|
}
|
|
1061
1127
|
/**
|
|
1128
|
+
* This function gives the word right before the whitespace that ends `text.slice(0, end)`, as the
|
|
1129
|
+
* regular expression `([A-Za-z][\w.-]*)\s+$` does. The word starts with a letter. The function
|
|
1130
|
+
* reads only the characters of the word and of the whitespace.
|
|
1131
|
+
*/
|
|
1132
|
+
function wordBefore(text, end) {
|
|
1133
|
+
let stop = end;
|
|
1134
|
+
while (stop > 0 && SPACE.test(text[stop - 1] ?? "")) stop--;
|
|
1135
|
+
if (stop === end) return void 0;
|
|
1136
|
+
let start = stop;
|
|
1137
|
+
while (start > 0 && /[\w.-]/.test(text[start - 1] ?? "")) start--;
|
|
1138
|
+
while (start < stop && !/[A-Za-z]/.test(text[start] ?? "")) start++;
|
|
1139
|
+
return start < stop ? text.slice(start, stop) : void 0;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1062
1142
|
* This function gives the links of a cell: each Markdown link, then each bare reference in the rest
|
|
1063
1143
|
* of the text. A reference is a pull request ("PR #12", "#12") or a commit hash in a code span.
|
|
1064
1144
|
*
|
|
@@ -1077,7 +1157,7 @@ function linksOf(cell, options = {}) {
|
|
|
1077
1157
|
url
|
|
1078
1158
|
});
|
|
1079
1159
|
};
|
|
1080
|
-
const rest = String(cell ?? "")
|
|
1160
|
+
const rest = replaceLinks(String(cell ?? ""), true, (label, target) => {
|
|
1081
1161
|
add(plainText(label), linkUrl(target, options));
|
|
1082
1162
|
return " ";
|
|
1083
1163
|
});
|
|
@@ -1087,7 +1167,7 @@ function linksOf(cell, options = {}) {
|
|
|
1087
1167
|
let pulls = false;
|
|
1088
1168
|
const reference = /\bPRs?\s*#(\d+)|(?<![\w/&])#(\d+)\b|`([0-9a-f]{7,40})`/g;
|
|
1089
1169
|
for (let match = reference.exec(rest); match; match = reference.exec(rest)) {
|
|
1090
|
-
const word =
|
|
1170
|
+
const word = wordBefore(rest, match.index);
|
|
1091
1171
|
if (word && !NOT_REPOSITORIES.has(word.toLowerCase())) current = word;
|
|
1092
1172
|
const prefix = current === defaultRepo ? "" : `${current} `;
|
|
1093
1173
|
if (match[1] !== void 0) pulls = true;
|
|
@@ -1097,15 +1177,30 @@ function linksOf(cell, options = {}) {
|
|
|
1097
1177
|
}
|
|
1098
1178
|
return links;
|
|
1099
1179
|
}
|
|
1100
|
-
/**
|
|
1101
|
-
|
|
1180
|
+
/**
|
|
1181
|
+
* The heading of an item: "### `id`: Title". A dash can take the place of the colon. The title goes
|
|
1182
|
+
* to `closeHeading`.
|
|
1183
|
+
*/
|
|
1184
|
+
const ITEM_HEADING = /^###\s+`([^`]+)`\s*[:–—-]\s*(\S[\s\S]*)?$/;
|
|
1102
1185
|
/** The other heading of an item: "### S1 · Title". The ID is one word before the middle dot. */
|
|
1103
|
-
const ITEM_HEADING_DOT = /^###\s+([A-Za-z0-9][\w.-]*)\s+·\s+(
|
|
1186
|
+
const ITEM_HEADING_DOT = /^###\s+([A-Za-z0-9][\w.-]*)\s+·\s+(\S[\s\S]*)?$/;
|
|
1187
|
+
/** This function gives the ID and the title of an item heading, or null for another line. */
|
|
1188
|
+
function itemHeadingOf(line) {
|
|
1189
|
+
for (const pattern of [ITEM_HEADING, ITEM_HEADING_DOT]) {
|
|
1190
|
+
const match = pattern.exec(line);
|
|
1191
|
+
const title = match ? closeHeading(match[2] ?? "") : null;
|
|
1192
|
+
if (match && title !== null) return {
|
|
1193
|
+
id: match[1] ?? "",
|
|
1194
|
+
title
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1104
1199
|
/**
|
|
1105
1200
|
* A field of an item heading: "- Area: Docs", "- **Area:** Docs" or "- **Area**: Docs". The label
|
|
1106
1201
|
* matches without case.
|
|
1107
1202
|
*/
|
|
1108
|
-
const ITEM_FIELD = /^\s*[-*]\s+(\*\*|__)?([A-Za-z][A-Za-z ]*?)(?::\1|\1:)\s*(.*)
|
|
1203
|
+
const ITEM_FIELD = /^\s*[-*]\s+(\*\*|__)?([A-Za-z][A-Za-z ]*?)(?::\1|\1:)\s*(\S.*)?$/;
|
|
1109
1204
|
/** The fields of an item heading that give the optional fields of an item. */
|
|
1110
1205
|
const ITEM_FIELDS = {
|
|
1111
1206
|
area: "area",
|
|
@@ -1138,20 +1233,20 @@ function headingItems(markdown, options) {
|
|
|
1138
1233
|
continue;
|
|
1139
1234
|
}
|
|
1140
1235
|
if (fence !== null) continue;
|
|
1141
|
-
const heading =
|
|
1236
|
+
const heading = headingOf(line);
|
|
1142
1237
|
if (heading) {
|
|
1143
|
-
const level = heading
|
|
1238
|
+
const { level } = heading;
|
|
1144
1239
|
if (level > 3) continue;
|
|
1145
1240
|
current = void 0;
|
|
1146
|
-
if (level === 2) section = plainText(heading
|
|
1241
|
+
if (level === 2) section = plainText(heading.text);
|
|
1147
1242
|
if (level === 1) section = "";
|
|
1148
1243
|
if (level !== 3) continue;
|
|
1149
|
-
const item =
|
|
1150
|
-
const id = plainText(item?.
|
|
1244
|
+
const item = itemHeadingOf(line);
|
|
1245
|
+
const id = plainText(item?.id);
|
|
1151
1246
|
if (!item || isEmpty(id)) continue;
|
|
1152
1247
|
current = {
|
|
1153
1248
|
id,
|
|
1154
|
-
title: item
|
|
1249
|
+
title: item.title,
|
|
1155
1250
|
status: section,
|
|
1156
1251
|
statusClass: statusClassOf(section),
|
|
1157
1252
|
section,
|
|
@@ -1310,13 +1405,29 @@ function escapeHtml(text) {
|
|
|
1310
1405
|
return String(text).replace(/[&<>"']/g, (char) => ESCAPES[char] ?? char);
|
|
1311
1406
|
}
|
|
1312
1407
|
/**
|
|
1408
|
+
* This function removes the control characters and the spaces (U+0000 to U+0020) at the two ends
|
|
1409
|
+
* of a text. A loop does it, because the regular expression `[\u0000- ]+$` backtracks. For a long
|
|
1410
|
+
* run of these characters, its time grows with the square of the length.
|
|
1411
|
+
*/
|
|
1412
|
+
function trimControl(text) {
|
|
1413
|
+
let start = 0;
|
|
1414
|
+
let end = text.length;
|
|
1415
|
+
while (start < end && text.charCodeAt(start) <= 32) start++;
|
|
1416
|
+
while (end > start && text.charCodeAt(end - 1) <= 32) end--;
|
|
1417
|
+
return text.slice(start, end);
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1313
1420
|
* This function gives a URL that a link can hold, or null. It accepts `http:`, `https:` and
|
|
1314
1421
|
* `mailto:` URLs, and relative URLs. It refuses each other scheme, for example `javascript:`.
|
|
1422
|
+
*
|
|
1423
|
+
* The URL parser of a browser removes each tab and line break, and the control characters and
|
|
1424
|
+
* spaces at the two ends. This function removes them before the scheme check, as the browser does.
|
|
1425
|
+
* Thus a control character before `javascript:` does not hide the scheme.
|
|
1315
1426
|
*/
|
|
1316
1427
|
function safeUrl(url) {
|
|
1317
|
-
const trimmed = url.trim();
|
|
1428
|
+
const trimmed = trimControl(url.trim().replace(/[\t\n\r]/g, ""));
|
|
1318
1429
|
if (/^(?:https?:|mailto:)/i.test(trimmed)) return trimmed;
|
|
1319
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || trimmed
|
|
1430
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) || /^[/\\]{2}/.test(trimmed)) return null;
|
|
1320
1431
|
return trimmed;
|
|
1321
1432
|
}
|
|
1322
1433
|
/** This function gives an `<a>` element, or the escaped text when the URL is not safe. */
|
|
@@ -1914,7 +2025,11 @@ document.addEventListener("click", (event) => {
|
|
|
1914
2025
|
const label = button.querySelector("[data-sp-label]");
|
|
1915
2026
|
if (label) label.textContent = open ? button.dataset.less : button.dataset.more;
|
|
1916
2027
|
});
|
|
1917
|
-
|
|
2028
|
+
// A malformed hash, for example "#100%", must not stop the module
|
|
2029
|
+
let hashed = "";
|
|
2030
|
+
try {
|
|
2031
|
+
hashed = decodeURIComponent(location.hash.slice(1));
|
|
2032
|
+
} catch {}
|
|
1918
2033
|
for (const [version, panel] of panels) if (panel.id === hashed) select(version, null);
|
|
1919
2034
|
const revealAll = () => { for (const box of boxes) reveal(box, selected, false); };
|
|
1920
2035
|
revealAll();
|
|
@@ -2000,7 +2115,7 @@ function statusLead(data) {
|
|
|
2000
2115
|
}
|
|
2001
2116
|
/** This function gives the part of a URL for a text: "The server stack (R37)" gives "the-server-stack-r37". */
|
|
2002
2117
|
function slugOf(text) {
|
|
2003
|
-
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(
|
|
2118
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "other";
|
|
2004
2119
|
}
|
|
2005
2120
|
/** This function gives the sections of the items, in the sequence of the file. */
|
|
2006
2121
|
function sectionsOf(items) {
|
|
@@ -2420,4 +2535,4 @@ async function runCli(argv, io) {
|
|
|
2420
2535
|
//#endregion
|
|
2421
2536
|
export { GITHUB_API as $, RoadmapParseError as A, DEFAULT_CHANGELOGS as B, longDate as C, inlineMarkdown as D, escapeHtml as E, plainText as F, globFiles as G, mergeReleases as H, readStatusBoard as I, headingDate as J, gitTagDates as K, roadmapItems as L, buildStatusBoard as M, linksOf as N, safeUrl as O, markdownTables as P, npmPublishTimes as Q, splitRow as R, dayRange as S, utcDay as T, readChangelogs as U, ReleaseDataError as V, readReleases as W, originRepository as X, isShallow as Y, NPM_REGISTRY as Z, oldestFirst as _, runCli as a, headingAnchor as at, BASE_CSS as b, openGroups as c, compareVersions as ct, slugOf as d, versionOfTag as dt, GITHUB_GRAPHQL as et, statusLead as f, laneCenter as g, renderReleasesPage as h, parseBuildArgs as i, firstSentence as it, STATUS_CLASSES as j, OPEN_CLASSES as k, renderStatusPage as l, kindOf as lt, releasesLead as m, UsageError as n, githubOpenWork as nt, STATUS_SCRIPT as o, parseChangelog as ot, RELEASES_SCRIPT as p, headCommit as q, build as r, githubReleases as rt, classCounts as s, releaseBodyEntries as st, HELP as t, checkStateOf as tt, sectionsOf as u, parseVersion as ut, releaseSummary as v, shortDate as w, pageShell as x, timelineLayout as y, statusClassOf as z };
|
|
2422
2537
|
|
|
2423
|
-
//# sourceMappingURL=build-
|
|
2538
|
+
//# sourceMappingURL=build-MO5a6V2k.js.map
|