@wdprlib/runtime 0.1.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/index.cjs +890 -0
- package/dist/index.d.cts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +857 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
// packages/runtime/src/utils/dom.ts
|
|
2
|
+
function isElement(target) {
|
|
3
|
+
return target !== null && "nodeType" in target && target.nodeType === 1;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// packages/runtime/src/utils/tooltip.ts
|
|
7
|
+
var activeTooltip = null;
|
|
8
|
+
function showTooltipEl(anchor, tip) {
|
|
9
|
+
hideTooltip();
|
|
10
|
+
const doc = anchor.ownerDocument;
|
|
11
|
+
tip.style.position = "absolute";
|
|
12
|
+
tip.style.zIndex = "10000";
|
|
13
|
+
doc.body.appendChild(tip);
|
|
14
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
15
|
+
const tipRect = tip.getBoundingClientRect();
|
|
16
|
+
const win = doc.defaultView ?? window;
|
|
17
|
+
let left = anchorRect.left + win.scrollX;
|
|
18
|
+
let top = anchorRect.bottom + win.scrollY + 4;
|
|
19
|
+
if (left + tipRect.width > win.innerWidth + win.scrollX) {
|
|
20
|
+
left = win.innerWidth + win.scrollX - tipRect.width - 8;
|
|
21
|
+
}
|
|
22
|
+
if (top + tipRect.height > win.innerHeight + win.scrollY) {
|
|
23
|
+
top = anchorRect.top + win.scrollY - tipRect.height - 4;
|
|
24
|
+
}
|
|
25
|
+
tip.style.left = `${left}px`;
|
|
26
|
+
tip.style.top = `${top}px`;
|
|
27
|
+
activeTooltip = tip;
|
|
28
|
+
}
|
|
29
|
+
function showTooltip(anchor, source) {
|
|
30
|
+
const doc = anchor.ownerDocument;
|
|
31
|
+
const tip = doc.createElement("div");
|
|
32
|
+
tip.className = "hovertip";
|
|
33
|
+
tip.style.width = "auto";
|
|
34
|
+
tip.style.backgroundColor = "white";
|
|
35
|
+
const content = doc.createElement("div");
|
|
36
|
+
content.className = "content";
|
|
37
|
+
const clone = source.cloneNode(true);
|
|
38
|
+
while (clone.firstChild) {
|
|
39
|
+
content.appendChild(clone.firstChild);
|
|
40
|
+
}
|
|
41
|
+
tip.appendChild(content);
|
|
42
|
+
showTooltipEl(anchor, tip);
|
|
43
|
+
}
|
|
44
|
+
function hideTooltip() {
|
|
45
|
+
if (activeTooltip) {
|
|
46
|
+
activeTooltip.remove();
|
|
47
|
+
activeTooltip = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// packages/runtime/src/bibcite.ts
|
|
52
|
+
function initBibcite(root) {
|
|
53
|
+
function handleMouseEnter(e) {
|
|
54
|
+
const target = e.target;
|
|
55
|
+
if (!isElement(target))
|
|
56
|
+
return;
|
|
57
|
+
const link = target.closest("a.bibcite");
|
|
58
|
+
if (!link)
|
|
59
|
+
return;
|
|
60
|
+
const href = link.getAttribute("href");
|
|
61
|
+
if (!href?.startsWith("#"))
|
|
62
|
+
return;
|
|
63
|
+
const bibitem = root.ownerDocument.getElementById(href.slice(1));
|
|
64
|
+
if (!bibitem)
|
|
65
|
+
return;
|
|
66
|
+
const id = href.slice(1).replace(/^bibitem-/, "");
|
|
67
|
+
const tip = buildBibciteTooltip(root.ownerDocument, bibitem, id);
|
|
68
|
+
showTooltipEl(link, tip);
|
|
69
|
+
}
|
|
70
|
+
function handleMouseLeave(e) {
|
|
71
|
+
const target = e.target;
|
|
72
|
+
if (!isElement(target))
|
|
73
|
+
return;
|
|
74
|
+
if (target.closest("a.bibcite")) {
|
|
75
|
+
hideTooltip();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
root.addEventListener("mouseenter", handleMouseEnter, true);
|
|
79
|
+
root.addEventListener("mouseleave", handleMouseLeave, true);
|
|
80
|
+
return {
|
|
81
|
+
destroy() {
|
|
82
|
+
root.removeEventListener("mouseenter", handleMouseEnter, true);
|
|
83
|
+
root.removeEventListener("mouseleave", handleMouseLeave, true);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function buildBibciteTooltip(doc, bibitemEl, id) {
|
|
88
|
+
const tip = doc.createElement("div");
|
|
89
|
+
tip.className = "hovertip";
|
|
90
|
+
tip.style.width = "auto";
|
|
91
|
+
tip.style.backgroundColor = "white";
|
|
92
|
+
const content = doc.createElement("div");
|
|
93
|
+
content.className = "content";
|
|
94
|
+
const refWrap = doc.createElement("div");
|
|
95
|
+
refWrap.className = "reference";
|
|
96
|
+
const heading = doc.createElement("div");
|
|
97
|
+
heading.className = "r-heading";
|
|
98
|
+
heading.textContent = `Reference ${id}.`;
|
|
99
|
+
refWrap.appendChild(heading);
|
|
100
|
+
const rContent = doc.createElement("div");
|
|
101
|
+
rContent.className = "r-content";
|
|
102
|
+
const clone = bibitemEl.cloneNode(true);
|
|
103
|
+
const firstText = clone.firstChild;
|
|
104
|
+
if (firstText?.nodeType === Node.TEXT_NODE) {
|
|
105
|
+
firstText.textContent = firstText.textContent?.replace(/^\s*[0-9]+\.\s*/, "") ?? "";
|
|
106
|
+
}
|
|
107
|
+
while (clone.firstChild)
|
|
108
|
+
rContent.appendChild(clone.firstChild);
|
|
109
|
+
refWrap.appendChild(rContent);
|
|
110
|
+
const footer = doc.createElement("div");
|
|
111
|
+
footer.className = "r-footer";
|
|
112
|
+
footer.textContent = "(click to scroll to bibliography)";
|
|
113
|
+
refWrap.appendChild(footer);
|
|
114
|
+
content.appendChild(refWrap);
|
|
115
|
+
tip.appendChild(content);
|
|
116
|
+
return tip;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// packages/runtime/src/collapsible.ts
|
|
120
|
+
function initCollapsible(root, options) {
|
|
121
|
+
const useFade = options?.fade !== false;
|
|
122
|
+
function handleClick(e) {
|
|
123
|
+
const target = e.target;
|
|
124
|
+
if (!isElement(target))
|
|
125
|
+
return;
|
|
126
|
+
const link = target.closest("a.collapsible-block-link");
|
|
127
|
+
if (!link)
|
|
128
|
+
return;
|
|
129
|
+
const block = link.closest(".collapsible-block");
|
|
130
|
+
if (!block)
|
|
131
|
+
return;
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
const folded = block.querySelector(".collapsible-block-folded");
|
|
134
|
+
const unfolded = block.querySelector(".collapsible-block-unfolded");
|
|
135
|
+
if (!folded || !unfolded)
|
|
136
|
+
return;
|
|
137
|
+
const isFolded = folded.style.display !== "none";
|
|
138
|
+
if (isFolded) {
|
|
139
|
+
folded.style.display = "none";
|
|
140
|
+
unfolded.style.display = "block";
|
|
141
|
+
if (useFade) {
|
|
142
|
+
const content = unfolded.querySelector(".collapsible-block-content");
|
|
143
|
+
if (content)
|
|
144
|
+
fadeIn(content);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
folded.style.display = "block";
|
|
148
|
+
unfolded.style.display = "none";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
root.addEventListener("click", handleClick);
|
|
152
|
+
return {
|
|
153
|
+
destroy() {
|
|
154
|
+
root.removeEventListener("click", handleClick);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function fadeIn(el) {
|
|
159
|
+
el.style.opacity = "0";
|
|
160
|
+
el.style.transition = "opacity 200ms ease-in";
|
|
161
|
+
el.offsetHeight;
|
|
162
|
+
el.style.opacity = "1";
|
|
163
|
+
const onEnd = () => {
|
|
164
|
+
el.style.transition = "";
|
|
165
|
+
el.style.opacity = "";
|
|
166
|
+
el.removeEventListener("transitionend", onEnd);
|
|
167
|
+
};
|
|
168
|
+
el.addEventListener("transitionend", onEnd);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// packages/runtime/src/email.ts
|
|
172
|
+
function isValidEmail(email) {
|
|
173
|
+
return /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
|
|
174
|
+
}
|
|
175
|
+
function initEmail(root) {
|
|
176
|
+
const elements = root.querySelectorAll("span.wiki-email");
|
|
177
|
+
for (const el of elements) {
|
|
178
|
+
processEmail(el);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function processEmail(el) {
|
|
182
|
+
const obfuscated = el.textContent;
|
|
183
|
+
if (!obfuscated)
|
|
184
|
+
return;
|
|
185
|
+
const reversed = obfuscated.split("").reverse().join("");
|
|
186
|
+
const email = reversed.replace(/\|/g, "@");
|
|
187
|
+
if (!isValidEmail(email)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const doc = el.ownerDocument;
|
|
191
|
+
const link = doc.createElement("a");
|
|
192
|
+
link.href = `mailto:${email}`;
|
|
193
|
+
link.textContent = email;
|
|
194
|
+
el.replaceWith(link);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// packages/runtime/src/foldable-list.ts
|
|
198
|
+
function initFoldableList(root) {
|
|
199
|
+
const containers = root.querySelectorAll(".foldable-list-container");
|
|
200
|
+
for (const container of containers) {
|
|
201
|
+
setupFoldableList(container);
|
|
202
|
+
}
|
|
203
|
+
function handleContainerClick(e) {
|
|
204
|
+
const target = e.target;
|
|
205
|
+
if (!isElement(target))
|
|
206
|
+
return;
|
|
207
|
+
if (target.tagName === "A" && target.href !== "#" && target.href !== "javascript:;") {
|
|
208
|
+
const href = target.getAttribute("href");
|
|
209
|
+
if (href !== "#" && href !== "javascript:;") {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
let li = target;
|
|
215
|
+
while (li && li.tagName?.toLowerCase() !== "li") {
|
|
216
|
+
li = li.parentElement;
|
|
217
|
+
}
|
|
218
|
+
if (!li)
|
|
219
|
+
return;
|
|
220
|
+
if (!li.classList.contains("folded") && !li.classList.contains("unfolded")) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (li.classList.contains("folded")) {
|
|
224
|
+
li.classList.remove("folded");
|
|
225
|
+
li.classList.add("unfolded");
|
|
226
|
+
} else {
|
|
227
|
+
li.classList.remove("unfolded");
|
|
228
|
+
li.classList.add("folded");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const container of containers) {
|
|
232
|
+
container.addEventListener("click", handleContainerClick);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
destroy() {
|
|
236
|
+
for (const container of containers) {
|
|
237
|
+
container.removeEventListener("click", handleContainerClick);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function setupFoldableList(container) {
|
|
243
|
+
const items = container.querySelectorAll("li");
|
|
244
|
+
for (const item of items) {
|
|
245
|
+
const nestedList = item.querySelector(":scope > ul, :scope > ol");
|
|
246
|
+
if (!nestedList)
|
|
247
|
+
continue;
|
|
248
|
+
if (item.querySelector(".foldable-list-toggle"))
|
|
249
|
+
continue;
|
|
250
|
+
item.classList.add("folded");
|
|
251
|
+
const toggle = container.ownerDocument.createElement("a");
|
|
252
|
+
toggle.className = "foldable-list-toggle";
|
|
253
|
+
toggle.href = "javascript:;";
|
|
254
|
+
item.insertBefore(toggle, item.firstChild);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// packages/runtime/src/utils/scroll.ts
|
|
259
|
+
function scrollToElement(id, doc) {
|
|
260
|
+
const d = doc ?? document;
|
|
261
|
+
const el = d.getElementById(id);
|
|
262
|
+
if (!el)
|
|
263
|
+
return;
|
|
264
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
265
|
+
el.classList.add("wdpr-blink");
|
|
266
|
+
setTimeout(() => {
|
|
267
|
+
el.classList.remove("wdpr-blink");
|
|
268
|
+
}, 1500);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// packages/runtime/src/footnote.ts
|
|
272
|
+
function initFootnote(root) {
|
|
273
|
+
const doc = root.ownerDocument;
|
|
274
|
+
const container = doc.createElement("div");
|
|
275
|
+
container.id = "odialog-hovertips";
|
|
276
|
+
container.style.position = "absolute";
|
|
277
|
+
container.style.zIndex = "100";
|
|
278
|
+
container.style.top = "0";
|
|
279
|
+
container.style.width = "100%";
|
|
280
|
+
doc.body.appendChild(container);
|
|
281
|
+
const tooltipMap = new Map;
|
|
282
|
+
const frefs = root.querySelectorAll("a.footnoteref");
|
|
283
|
+
for (const fref of frefs) {
|
|
284
|
+
const id = getFootnoteId(fref);
|
|
285
|
+
if (!id)
|
|
286
|
+
continue;
|
|
287
|
+
const footnote = doc.getElementById(id);
|
|
288
|
+
if (!footnote)
|
|
289
|
+
continue;
|
|
290
|
+
const numId = id.replace(/^footnote-/, "");
|
|
291
|
+
const tip = buildFootnoteTooltip(doc, footnote, numId);
|
|
292
|
+
container.appendChild(tip);
|
|
293
|
+
tooltipMap.set(fref.id, tip);
|
|
294
|
+
}
|
|
295
|
+
function handleClick(e) {
|
|
296
|
+
const target = e.target;
|
|
297
|
+
if (!isElement(target))
|
|
298
|
+
return;
|
|
299
|
+
const link = target.closest("a");
|
|
300
|
+
if (!link)
|
|
301
|
+
return;
|
|
302
|
+
if (link.classList.contains("footnoteref")) {
|
|
303
|
+
e.preventDefault();
|
|
304
|
+
const id = getFootnoteId(link);
|
|
305
|
+
if (id)
|
|
306
|
+
scrollToElement(id, doc);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const footnoteItem = link.closest(".footnote-footer");
|
|
310
|
+
if (footnoteItem) {
|
|
311
|
+
const backrefId = getBackrefId(link, footnoteItem);
|
|
312
|
+
if (backrefId) {
|
|
313
|
+
e.preventDefault();
|
|
314
|
+
scrollToElement(backrefId, doc);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function handleMouseEnter(e) {
|
|
319
|
+
const target = e.target;
|
|
320
|
+
if (!isElement(target))
|
|
321
|
+
return;
|
|
322
|
+
const link = target.closest("a.footnoteref");
|
|
323
|
+
if (!link)
|
|
324
|
+
return;
|
|
325
|
+
const tip = tooltipMap.get(link.id);
|
|
326
|
+
if (!tip)
|
|
327
|
+
return;
|
|
328
|
+
positionTooltip(tip, link);
|
|
329
|
+
tip.style.display = "block";
|
|
330
|
+
}
|
|
331
|
+
function handleMouseLeave(e) {
|
|
332
|
+
const target = e.target;
|
|
333
|
+
if (!isElement(target))
|
|
334
|
+
return;
|
|
335
|
+
if (target.closest("a.footnoteref")) {
|
|
336
|
+
for (const tip of tooltipMap.values()) {
|
|
337
|
+
tip.style.display = "none";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
root.addEventListener("click", handleClick);
|
|
342
|
+
root.addEventListener("mouseenter", handleMouseEnter, true);
|
|
343
|
+
root.addEventListener("mouseleave", handleMouseLeave, true);
|
|
344
|
+
return {
|
|
345
|
+
destroy() {
|
|
346
|
+
root.removeEventListener("click", handleClick);
|
|
347
|
+
root.removeEventListener("mouseenter", handleMouseEnter, true);
|
|
348
|
+
root.removeEventListener("mouseleave", handleMouseLeave, true);
|
|
349
|
+
container.remove();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function positionTooltip(tip, anchor) {
|
|
354
|
+
const doc = anchor.ownerDocument;
|
|
355
|
+
const win = doc.defaultView ?? window;
|
|
356
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
357
|
+
let left = anchorRect.left + win.scrollX;
|
|
358
|
+
let top = anchorRect.bottom + win.scrollY + 4;
|
|
359
|
+
tip.style.display = "block";
|
|
360
|
+
const tipRect = tip.getBoundingClientRect();
|
|
361
|
+
tip.style.display = "none";
|
|
362
|
+
if (left + tipRect.width > win.innerWidth + win.scrollX) {
|
|
363
|
+
left = win.innerWidth + win.scrollX - tipRect.width - 8;
|
|
364
|
+
}
|
|
365
|
+
if (top + tipRect.height > win.innerHeight + win.scrollY) {
|
|
366
|
+
top = anchorRect.top + win.scrollY - tipRect.height - 4;
|
|
367
|
+
}
|
|
368
|
+
tip.style.left = `${left}px`;
|
|
369
|
+
tip.style.top = `${top}px`;
|
|
370
|
+
}
|
|
371
|
+
function buildFootnoteTooltip(doc, footnoteEl, id) {
|
|
372
|
+
const tip = doc.createElement("div");
|
|
373
|
+
tip.className = "hovertip";
|
|
374
|
+
tip.style.width = "auto";
|
|
375
|
+
tip.style.backgroundColor = "white";
|
|
376
|
+
tip.style.position = "absolute";
|
|
377
|
+
tip.style.display = "none";
|
|
378
|
+
tip.style.border = "1px solid black";
|
|
379
|
+
const content = doc.createElement("div");
|
|
380
|
+
content.className = "content";
|
|
381
|
+
const footnoteWrap = doc.createElement("div");
|
|
382
|
+
footnoteWrap.className = "footnote";
|
|
383
|
+
const heading = doc.createElement("div");
|
|
384
|
+
heading.className = "f-heading";
|
|
385
|
+
heading.textContent = `Footnote ${id}.`;
|
|
386
|
+
footnoteWrap.appendChild(heading);
|
|
387
|
+
const fContent = doc.createElement("div");
|
|
388
|
+
fContent.className = "f-content";
|
|
389
|
+
const clone = footnoteEl.cloneNode(true);
|
|
390
|
+
const firstLink = clone.querySelector("a");
|
|
391
|
+
if (firstLink) {
|
|
392
|
+
const next = firstLink.nextSibling;
|
|
393
|
+
if (next?.nodeType === Node.TEXT_NODE && next.textContent?.startsWith(". ")) {
|
|
394
|
+
next.textContent = next.textContent.slice(2);
|
|
395
|
+
}
|
|
396
|
+
firstLink.remove();
|
|
397
|
+
}
|
|
398
|
+
while (clone.firstChild)
|
|
399
|
+
fContent.appendChild(clone.firstChild);
|
|
400
|
+
footnoteWrap.appendChild(fContent);
|
|
401
|
+
const footer = doc.createElement("div");
|
|
402
|
+
footer.className = "f-footer";
|
|
403
|
+
footer.textContent = "(click to scroll to footnotes)";
|
|
404
|
+
footnoteWrap.appendChild(footer);
|
|
405
|
+
content.appendChild(footnoteWrap);
|
|
406
|
+
tip.appendChild(content);
|
|
407
|
+
return tip;
|
|
408
|
+
}
|
|
409
|
+
function getFootnoteId(link) {
|
|
410
|
+
const href = link.getAttribute("href");
|
|
411
|
+
if (href?.startsWith("#"))
|
|
412
|
+
return href.slice(1);
|
|
413
|
+
const id = link.id;
|
|
414
|
+
if (id.startsWith("footnoteref-")) {
|
|
415
|
+
return `footnote-${id.slice("footnoteref-".length)}`;
|
|
416
|
+
}
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
function getBackrefId(link, footer) {
|
|
420
|
+
const href = link.getAttribute("href");
|
|
421
|
+
if (href?.startsWith("#"))
|
|
422
|
+
return href.slice(1);
|
|
423
|
+
if (footer.id.startsWith("footnote-")) {
|
|
424
|
+
const num = footer.id.slice("footnote-".length);
|
|
425
|
+
return `footnoteref-${num}`;
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// packages/runtime/src/html-block.ts
|
|
431
|
+
var MAX_HEIGHT = 1e5;
|
|
432
|
+
function isResizeMessage(data) {
|
|
433
|
+
if (typeof data !== "object" || data === null || data.type !== "wdpr-html-block-resize" || typeof data.height !== "number") {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const height = data.height;
|
|
437
|
+
return Number.isFinite(height) && height >= 0 && height <= MAX_HEIGHT;
|
|
438
|
+
}
|
|
439
|
+
function initHtmlBlockResize(root) {
|
|
440
|
+
function handleMessage(e) {
|
|
441
|
+
if (!isResizeMessage(e.data))
|
|
442
|
+
return;
|
|
443
|
+
const iframes = root.querySelectorAll("iframe.html-block-iframe");
|
|
444
|
+
for (const iframe of iframes) {
|
|
445
|
+
if (iframe.contentWindow === e.source) {
|
|
446
|
+
iframe.style.height = `${e.data.height}px`;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
window.addEventListener("message", handleMessage);
|
|
452
|
+
return {
|
|
453
|
+
destroy() {
|
|
454
|
+
window.removeEventListener("message", handleMessage);
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
var HTML_BLOCK_RESIZE_SCRIPT = `(function(){
|
|
459
|
+
function notifyHeight() {
|
|
460
|
+
var height = (document.documentElement.scrollHeight || document.body.scrollHeight) + 2;
|
|
461
|
+
parent.postMessage({ type: 'wdpr-html-block-resize', height: height }, '*');
|
|
462
|
+
}
|
|
463
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
464
|
+
new ResizeObserver(notifyHeight).observe(document.body);
|
|
465
|
+
} else {
|
|
466
|
+
setInterval(notifyHeight, 250);
|
|
467
|
+
}
|
|
468
|
+
if (document.readyState === 'loading') {
|
|
469
|
+
document.addEventListener('DOMContentLoaded', notifyHeight);
|
|
470
|
+
} else {
|
|
471
|
+
notifyHeight();
|
|
472
|
+
}
|
|
473
|
+
window.addEventListener('load', notifyHeight);
|
|
474
|
+
})();`;
|
|
475
|
+
|
|
476
|
+
// packages/runtime/src/math.ts
|
|
477
|
+
var scriptLoaded = false;
|
|
478
|
+
function initMath(root, options) {
|
|
479
|
+
const mathUrl = options?.mathUrl;
|
|
480
|
+
if (mathUrl) {
|
|
481
|
+
const hasMath = root.querySelector(".math-equation, .math-inline") !== null;
|
|
482
|
+
if (hasMath && !scriptLoaded) {
|
|
483
|
+
loadScript(root.ownerDocument, mathUrl);
|
|
484
|
+
scriptLoaded = true;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function handleMouseEnter(e) {
|
|
488
|
+
const target = e.target;
|
|
489
|
+
if (!isElement(target))
|
|
490
|
+
return;
|
|
491
|
+
const link = target.closest("a.eref");
|
|
492
|
+
if (!link)
|
|
493
|
+
return;
|
|
494
|
+
const href = link.getAttribute("href");
|
|
495
|
+
if (!href?.startsWith("#"))
|
|
496
|
+
return;
|
|
497
|
+
const equation = root.ownerDocument.getElementById(href.slice(1));
|
|
498
|
+
if (!equation)
|
|
499
|
+
return;
|
|
500
|
+
showTooltip(link, equation);
|
|
501
|
+
}
|
|
502
|
+
function handleMouseLeave(e) {
|
|
503
|
+
const target = e.target;
|
|
504
|
+
if (!isElement(target))
|
|
505
|
+
return;
|
|
506
|
+
if (target.closest("a.eref")) {
|
|
507
|
+
hideTooltip();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
root.addEventListener("mouseenter", handleMouseEnter, true);
|
|
511
|
+
root.addEventListener("mouseleave", handleMouseLeave, true);
|
|
512
|
+
return {
|
|
513
|
+
destroy() {
|
|
514
|
+
root.removeEventListener("mouseenter", handleMouseEnter, true);
|
|
515
|
+
root.removeEventListener("mouseleave", handleMouseLeave, true);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function isValidScriptUrl(url) {
|
|
520
|
+
try {
|
|
521
|
+
const parsed = new URL(url, globalThis.location?.href);
|
|
522
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
523
|
+
} catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
function loadScript(doc, url) {
|
|
528
|
+
if (!isValidScriptUrl(url)) {
|
|
529
|
+
console.error("[wdparser] Invalid script URL scheme:", url);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
const script = doc.createElement("script");
|
|
533
|
+
script.src = url;
|
|
534
|
+
script.async = true;
|
|
535
|
+
doc.head.appendChild(script);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// packages/runtime/src/modules/join.ts
|
|
539
|
+
function initJoin(root, options) {
|
|
540
|
+
const onJoin = options?.onJoin;
|
|
541
|
+
function handleClick(e) {
|
|
542
|
+
if (!onJoin)
|
|
543
|
+
return;
|
|
544
|
+
const target = e.target;
|
|
545
|
+
if (!isElement(target))
|
|
546
|
+
return;
|
|
547
|
+
const btn = target.closest(".join-btn");
|
|
548
|
+
if (!btn)
|
|
549
|
+
return;
|
|
550
|
+
e.preventDefault();
|
|
551
|
+
onJoin();
|
|
552
|
+
}
|
|
553
|
+
root.addEventListener("click", handleClick);
|
|
554
|
+
return {
|
|
555
|
+
destroy() {
|
|
556
|
+
root.removeEventListener("click", handleClick);
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// packages/runtime/src/modules/rate.ts
|
|
562
|
+
function initRate(root, options) {
|
|
563
|
+
const onRate = options?.onRate;
|
|
564
|
+
function handleClick(e) {
|
|
565
|
+
if (!onRate)
|
|
566
|
+
return;
|
|
567
|
+
const target = e.target;
|
|
568
|
+
if (!isElement(target))
|
|
569
|
+
return;
|
|
570
|
+
const btn = target.closest(".btn, .rateup, .ratedown, .cancel");
|
|
571
|
+
if (!btn)
|
|
572
|
+
return;
|
|
573
|
+
const widget = btn.closest(".page-rate-widget-box");
|
|
574
|
+
if (!widget)
|
|
575
|
+
return;
|
|
576
|
+
e.preventDefault();
|
|
577
|
+
const pageId = widget.dataset["pageId"] ?? "";
|
|
578
|
+
let points = 0;
|
|
579
|
+
if (btn.classList.contains("rateup") || btn.classList.contains("btn-rate-up")) {
|
|
580
|
+
points = 1;
|
|
581
|
+
} else if (btn.classList.contains("ratedown") || btn.classList.contains("btn-rate-down")) {
|
|
582
|
+
points = -1;
|
|
583
|
+
} else if (btn.classList.contains("cancel") || btn.classList.contains("btn-cancel")) {
|
|
584
|
+
points = 0;
|
|
585
|
+
}
|
|
586
|
+
onRate(pageId, points).then((result) => {
|
|
587
|
+
updateRateDisplay(widget, result.points, result.votes);
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
root.addEventListener("click", handleClick);
|
|
591
|
+
return {
|
|
592
|
+
destroy() {
|
|
593
|
+
root.removeEventListener("click", handleClick);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function updateRateDisplay(widget, points, votes) {
|
|
598
|
+
const rateNum = widget.querySelector(".rate-points .number");
|
|
599
|
+
if (rateNum) {
|
|
600
|
+
const prefix = points > 0 ? "+" : "";
|
|
601
|
+
rateNum.textContent = `${prefix}${points}`;
|
|
602
|
+
}
|
|
603
|
+
const voteCount = widget.querySelector(".vote-count");
|
|
604
|
+
if (voteCount) {
|
|
605
|
+
voteCount.textContent = String(votes);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// packages/runtime/src/odate.ts
|
|
610
|
+
var DAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
611
|
+
var DAYS_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
612
|
+
var MONTHS_SHORT = [
|
|
613
|
+
"Jan",
|
|
614
|
+
"Feb",
|
|
615
|
+
"Mar",
|
|
616
|
+
"Apr",
|
|
617
|
+
"May",
|
|
618
|
+
"Jun",
|
|
619
|
+
"Jul",
|
|
620
|
+
"Aug",
|
|
621
|
+
"Sep",
|
|
622
|
+
"Oct",
|
|
623
|
+
"Nov",
|
|
624
|
+
"Dec"
|
|
625
|
+
];
|
|
626
|
+
var MONTHS_LONG = [
|
|
627
|
+
"January",
|
|
628
|
+
"February",
|
|
629
|
+
"March",
|
|
630
|
+
"April",
|
|
631
|
+
"May",
|
|
632
|
+
"June",
|
|
633
|
+
"July",
|
|
634
|
+
"August",
|
|
635
|
+
"September",
|
|
636
|
+
"October",
|
|
637
|
+
"November",
|
|
638
|
+
"December"
|
|
639
|
+
];
|
|
640
|
+
function initOdate(root) {
|
|
641
|
+
const elements = root.querySelectorAll("span.odate");
|
|
642
|
+
for (const el of elements) {
|
|
643
|
+
processOdate(el);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function processOdate(el) {
|
|
647
|
+
const classes = el.className.split(/\s+/);
|
|
648
|
+
let timestamp = null;
|
|
649
|
+
let format = "%e %b %Y, %H:%M";
|
|
650
|
+
for (const cls of classes) {
|
|
651
|
+
if (cls.startsWith("time_")) {
|
|
652
|
+
const val = Number(cls.slice(5));
|
|
653
|
+
if (!Number.isNaN(val))
|
|
654
|
+
timestamp = val;
|
|
655
|
+
} else if (cls.startsWith("format%")) {
|
|
656
|
+
format = decodeFormat(cls.slice(6));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (timestamp === null)
|
|
660
|
+
return;
|
|
661
|
+
const date = new Date(timestamp * 1000);
|
|
662
|
+
el.textContent = formatDate(date, format);
|
|
663
|
+
}
|
|
664
|
+
function decodeFormat(encoded) {
|
|
665
|
+
try {
|
|
666
|
+
return decodeURIComponent(encoded.replace(/\|/g, "%"));
|
|
667
|
+
} catch {
|
|
668
|
+
return encoded;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function formatDate(date, format) {
|
|
672
|
+
let result = "";
|
|
673
|
+
let i = 0;
|
|
674
|
+
while (i < format.length) {
|
|
675
|
+
if (format[i] === "%" && i + 1 < format.length) {
|
|
676
|
+
i++;
|
|
677
|
+
const spec = format[i];
|
|
678
|
+
result += formatSpec(date, spec ?? "");
|
|
679
|
+
i++;
|
|
680
|
+
} else {
|
|
681
|
+
result += format[i];
|
|
682
|
+
i++;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return result;
|
|
686
|
+
}
|
|
687
|
+
function formatSpec(date, spec) {
|
|
688
|
+
switch (spec) {
|
|
689
|
+
case "Y":
|
|
690
|
+
return String(date.getFullYear());
|
|
691
|
+
case "y":
|
|
692
|
+
return String(date.getFullYear()).slice(-2);
|
|
693
|
+
case "m":
|
|
694
|
+
return pad2(date.getMonth() + 1);
|
|
695
|
+
case "d":
|
|
696
|
+
return pad2(date.getDate());
|
|
697
|
+
case "e":
|
|
698
|
+
return String(date.getDate());
|
|
699
|
+
case "H":
|
|
700
|
+
return pad2(date.getHours());
|
|
701
|
+
case "M":
|
|
702
|
+
return pad2(date.getMinutes());
|
|
703
|
+
case "S":
|
|
704
|
+
return pad2(date.getSeconds());
|
|
705
|
+
case "a":
|
|
706
|
+
return DAYS_SHORT[date.getDay()] ?? "";
|
|
707
|
+
case "A":
|
|
708
|
+
return DAYS_LONG[date.getDay()] ?? "";
|
|
709
|
+
case "b":
|
|
710
|
+
return MONTHS_SHORT[date.getMonth()] ?? "";
|
|
711
|
+
case "B":
|
|
712
|
+
return MONTHS_LONG[date.getMonth()] ?? "";
|
|
713
|
+
case "j":
|
|
714
|
+
return String(getDayOfYear(date));
|
|
715
|
+
case "O":
|
|
716
|
+
return getOrdinalSuffix(date.getDate());
|
|
717
|
+
case "%":
|
|
718
|
+
return "%";
|
|
719
|
+
default:
|
|
720
|
+
return `%${spec}`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function pad2(n) {
|
|
724
|
+
return n < 10 ? `0${n}` : String(n);
|
|
725
|
+
}
|
|
726
|
+
function getDayOfYear(date) {
|
|
727
|
+
const start = new Date(date.getFullYear(), 0, 0);
|
|
728
|
+
const diff = date.getTime() - start.getTime();
|
|
729
|
+
return Math.floor(diff / 86400000);
|
|
730
|
+
}
|
|
731
|
+
function getOrdinalSuffix(day) {
|
|
732
|
+
if (day >= 11 && day <= 13)
|
|
733
|
+
return `${day}th`;
|
|
734
|
+
switch (day % 10) {
|
|
735
|
+
case 1:
|
|
736
|
+
return `${day}st`;
|
|
737
|
+
case 2:
|
|
738
|
+
return `${day}nd`;
|
|
739
|
+
case 3:
|
|
740
|
+
return `${day}rd`;
|
|
741
|
+
default:
|
|
742
|
+
return `${day}th`;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// packages/runtime/src/tabview.ts
|
|
747
|
+
function initTabview(root) {
|
|
748
|
+
function handleClick(e) {
|
|
749
|
+
const target = e.target;
|
|
750
|
+
if (!isElement(target))
|
|
751
|
+
return;
|
|
752
|
+
const navItem = target.closest(".yui-navset .yui-nav li");
|
|
753
|
+
if (!navItem)
|
|
754
|
+
return;
|
|
755
|
+
const navset = navItem.closest(".yui-navset");
|
|
756
|
+
if (!navset)
|
|
757
|
+
return;
|
|
758
|
+
e.preventDefault();
|
|
759
|
+
const nav = navset.querySelector(".yui-nav");
|
|
760
|
+
const content = navset.querySelector(".yui-content");
|
|
761
|
+
if (!nav || !content)
|
|
762
|
+
return;
|
|
763
|
+
const navItems = Array.from(nav.querySelectorAll(":scope > li"));
|
|
764
|
+
const contentDivs = Array.from(content.querySelectorAll(":scope > div"));
|
|
765
|
+
const index = navItems.indexOf(navItem);
|
|
766
|
+
if (index === -1)
|
|
767
|
+
return;
|
|
768
|
+
for (const item of navItems) {
|
|
769
|
+
item.classList.remove("selected");
|
|
770
|
+
}
|
|
771
|
+
navItem.classList.add("selected");
|
|
772
|
+
for (let i = 0;i < contentDivs.length; i++) {
|
|
773
|
+
const div = contentDivs[i];
|
|
774
|
+
if (div) {
|
|
775
|
+
div.style.display = i === index ? "" : "none";
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
root.addEventListener("click", handleClick);
|
|
780
|
+
return {
|
|
781
|
+
destroy() {
|
|
782
|
+
root.removeEventListener("click", handleClick);
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// packages/runtime/src/toc.ts
|
|
788
|
+
function initToc(root) {
|
|
789
|
+
function handleClick(e) {
|
|
790
|
+
const target = e.target;
|
|
791
|
+
if (!isElement(target))
|
|
792
|
+
return;
|
|
793
|
+
const link = target.closest("a");
|
|
794
|
+
if (!link)
|
|
795
|
+
return;
|
|
796
|
+
const actionBar = link.closest("#toc-action-bar");
|
|
797
|
+
if (!actionBar)
|
|
798
|
+
return;
|
|
799
|
+
e.preventDefault();
|
|
800
|
+
const toc = actionBar.closest("#toc");
|
|
801
|
+
if (!toc)
|
|
802
|
+
return;
|
|
803
|
+
const tocList = toc.querySelector("#toc-list");
|
|
804
|
+
if (!tocList)
|
|
805
|
+
return;
|
|
806
|
+
const links = Array.from(actionBar.querySelectorAll("a"));
|
|
807
|
+
const foldLink = links[0];
|
|
808
|
+
const unfoldLink = links[1];
|
|
809
|
+
if (!foldLink || !unfoldLink)
|
|
810
|
+
return;
|
|
811
|
+
const isVisible = tocList.style.display !== "none";
|
|
812
|
+
if (isVisible) {
|
|
813
|
+
tocList.style.display = "none";
|
|
814
|
+
foldLink.style.display = "none";
|
|
815
|
+
unfoldLink.style.display = "";
|
|
816
|
+
} else {
|
|
817
|
+
tocList.style.display = "";
|
|
818
|
+
foldLink.style.display = "";
|
|
819
|
+
unfoldLink.style.display = "none";
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
root.addEventListener("click", handleClick);
|
|
823
|
+
return {
|
|
824
|
+
destroy() {
|
|
825
|
+
root.removeEventListener("click", handleClick);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// packages/runtime/src/index.ts
|
|
831
|
+
function initWdprRuntime(options) {
|
|
832
|
+
const root = options?.root ?? document.body;
|
|
833
|
+
const cleanups = [];
|
|
834
|
+
cleanups.push(initCollapsible(root, options));
|
|
835
|
+
cleanups.push(initTabview(root));
|
|
836
|
+
cleanups.push(initToc(root));
|
|
837
|
+
cleanups.push(initFootnote(root));
|
|
838
|
+
cleanups.push(initBibcite(root));
|
|
839
|
+
cleanups.push(initFoldableList(root));
|
|
840
|
+
cleanups.push(initRate(root, options));
|
|
841
|
+
cleanups.push(initJoin(root, options));
|
|
842
|
+
cleanups.push(initHtmlBlockResize(root));
|
|
843
|
+
cleanups.push(initMath(root, options));
|
|
844
|
+
initOdate(root);
|
|
845
|
+
initEmail(root);
|
|
846
|
+
return {
|
|
847
|
+
destroy() {
|
|
848
|
+
for (const cleanup of cleanups) {
|
|
849
|
+
cleanup.destroy();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
export {
|
|
855
|
+
initWdprRuntime,
|
|
856
|
+
HTML_BLOCK_RESIZE_SCRIPT
|
|
857
|
+
};
|