-
Notifications
You must be signed in to change notification settings - Fork 514
/
Copy pathweb.ts
218 lines (206 loc) · 7.85 KB
/
web.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import fs from "node:fs";
import path from "node:path";
import * as util from "./util.js";
import { CONTENT_ROOT } from "../../../libs/env/index.js";
import { KumaThis } from "../environment.js";
const DUMMY_BASE_URL = "https://example.com";
const _warned = new Map();
// The purpose of this function is to make sure `console.warn` is only called once
// per 'macro' per 'href'.
// There are some macros that use `smartLink` within themselves and these macros
// might be buggy and that's not the fault of the person using the wrapping
// macro. And because these might be used over and over and over we don't want
// to bombard stdout with warnings more than once.
// For example, there are X pages that use the CSS sidebar macro `CSSRef` and it
// might contain something like `smartLink(URL + 'oops', ...)` which leads to a
// broken link. But that problem lies with the `CSSRef.ejs` macro, which we
// don't entirely want to swallow and forget. But we don't want to point this
// out on every single page that *uses* that `CSSRef` macro.
function warnBrokenFlawByMacro(macro: string, href: string, notes: string) {
if (!_warned.has(macro)) {
_warned.set(macro, new Set());
}
if (!_warned.get(macro).has(href)) {
_warned.get(macro).add(href);
console.warn(`In ${macro} the smartLink to ${href} is broken! (${notes})`);
}
}
const web = {
// Insert a hyperlink.
link(uri, text, title, target) {
const out = [`<a href="${util.spacesToUnderscores(util.htmlEscape(uri))}"`];
if (title) {
out.push(` title="${util.htmlEscape(title)}"`);
}
if (target) {
out.push(` target="${util.htmlEscape(target)}"`);
}
out.push(">", util.htmlEscape(text || uri), "</a>");
return out.join("");
},
// Creates a hyperlink for given document location(href).
// e.g /en-US/docs/Web/HTML/Attributes
//
// For translated content, if the document doesn't exist
// then hyperlink to corresponding en-US document is returned.
smartLink(
this: KumaThis,
href: string,
title: string | null,
content: string | null = null,
subpath: string | null = null,
basepath: string | null = null,
ignoreFlawMacro: string | null = null
) {
let flaw;
let flawAttribute = "";
const page = this.info.getPageByURL(href);
// Get the pathname only (no hash) of the incoming "href" URI.
const hrefpath = this.info.getPathname(href);
// Save the hash portion, if any, for appending to the "href" attribute later.
const hrefhash = new URL(href, DUMMY_BASE_URL).hash;
if (page.url) {
if (hrefpath.toLowerCase() !== page.url.toLowerCase()) {
if (page.url.startsWith(basepath)) {
let suggested = page.url.replace(basepath, "");
if (
/\//.test(suggested) &&
!title &&
basepath.endsWith("/Web/API/")
) {
// This is the exception! When `smartLink` is used from the DOMxRef.ejs
// macro, the xref macro notation should use a `.` instead of a `/`.
// E.g. `{{domxref("GlobalEventHandlers.onload")}}
// because, when displayed we want the HTML to become:
//
// <a href="/en-US/docs/Web/API/GlobalEventHandlers/onload">
// <code>GlobalEventHandlers.onload</code>
// </a>
//
// Note the `<code>GlobalEventHandlers.onload</code>` label.
// However, some uses of DOMxRef uses a custom title. E.g.
// {{domxref("WindowOrWorkerGlobalScope/fetch","fetch()")}}
// which needs to become:
//
// <a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch">
// <code>fetch()</code>
// </a>
//
// So those with titles we ignore.
suggested = suggested.replace(/\//g, ".");
}
if (ignoreFlawMacro) {
warnBrokenFlawByMacro(
ignoreFlawMacro,
href,
`redirects to ${page.url}`
);
} else {
flaw = this.env.recordNonFatalError(
"redirected-link",
`${hrefpath} redirects to ${page.url}`,
{
current: subpath,
suggested,
}
);
flawAttribute = ` data-flaw-src="${util.htmlEscape(
flaw.macroSource
)}"`;
}
} else {
flaw = this.env.recordNonFatalError(
"wrong-xref-macro",
"wrong xref macro used (consider changing which macro you use)",
{
current: subpath,
}
);
flawAttribute = ` data-flaw-src="${util.htmlEscape(
flaw.macroSource
)}"`;
}
}
const titleAttribute = title ? ` title="${title}"` : "";
content ??= page.short_title ?? page.title;
return `<a href="${
page.url + hrefhash
}"${titleAttribute}${flawAttribute}>${content}</a>`;
}
if (!href.toLowerCase().startsWith("/en-us/")) {
// Before flagging this as a broken-link flaw, see if it's possible to
// change it to the en-US URL instead.
const hrefSplit = href.split("/");
hrefSplit[1] = "en-US";
const enUSPage = this.info.getPageByURL(hrefSplit.join("/"));
if (enUSPage.url) {
// But it's still a flaw. Record it so that translators can write a
// translated document to "fill the hole".
if (!ignoreFlawMacro) {
flaw = this.env.recordNonFatalError(
"broken-link",
`${hrefpath} does not exist but fell back to ${enUSPage.url}`
);
flawAttribute = ` data-flaw-src="${util.htmlEscape(
flaw.macroSource
)}"`;
}
content ??= enUSPage.short_title ?? enUSPage.title;
return (
'<a class="only-in-en-us" ' +
'title="Currently only available in English (US)" ' +
`href="${enUSPage.url}"${flawAttribute}>${content} <small>(en-US)</small></a>`
);
}
}
if (ignoreFlawMacro) {
warnBrokenFlawByMacro(ignoreFlawMacro, href, "does not exist");
} else {
flaw = this.env.recordNonFatalError(
"broken-link",
`${hrefpath} does not exist`
);
flawAttribute = ` data-flaw-src="${util.htmlEscape(flaw.macroSource)}"`;
}
// Let's get a potentially localized title for when the document is missing.
const titleWhenMissing = (this.mdn as any).getLocalString(
this.web.getJSONData("L10n-Common"),
"summary"
);
content ??= href;
return `<a class="page-not-created" title="${titleWhenMissing}"${flawAttribute}>${content}</a>`;
},
// Try calling "decodeURIComponent", but if there's an error, just
// return the text unmodified.
safeDecodeURIComponent: util.safeDecodeURIComponent,
// Given a URL, convert all spaces to underscores. This lets us fix a
// bunch of places where templates assume this is done automatically
// by the API, like MindTouch did.
spacesToUnderscores: util.spacesToUnderscores,
// Turn the text content of a header into a slug for use in an ID.
// Remove unsafe characters, and collapse whitespace gaps into
// underscores.
slugify: util.slugify,
// Return specific .json files from the content root
getJSONData(name) {
const filePath = path.join(CONTENT_ROOT, "jsondata", `${name}.json`);
try {
return readJSONDataFile(filePath);
} catch (error) {
console.error(`Tried to read JSON from ${filePath}`, error);
throw error;
}
},
};
const _readJSONDataCache = new Map();
function readJSONDataFile(filePath) {
if (_readJSONDataCache.has(filePath)) {
return _readJSONDataCache.get(filePath);
}
const payload = JSON.parse(fs.readFileSync(filePath, "utf-8"));
if (process.env.NODE_ENV === "production") {
_readJSONDataCache.set(filePath, payload);
}
return payload;
}
export default web;