-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathcustom.js
More file actions
282 lines (244 loc) · 9.02 KB
/
Copy pathcustom.js
File metadata and controls
282 lines (244 loc) · 9.02 KB
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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
function getUrlParameter(name) {
name = name.replace(/[\[\]]/g, "\\$&");
let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
let results = regex.exec(window.location.href);
if (!results) return null;
if (!results[2]) return "";
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function initializeLanguage(forcedLang = null) {
let lang = forcedLang || getUrlParameter("lang");
if (lang !== "js") {
lang = "ts";
}
// set top-level CSS class based on current language
document.body.classList.remove("page-language-js", "page-language-ts"); // for later switching
document.body.classList.add("page-language-" + lang);
return lang;
}
/* this function finds all tags which have both CSS classes "language-ts" and "highlighter-rouge"
and have exactly one direct neighbor sibling tag, which has the classes "language-ts" and "highlighter-rouge".
It then wraps both tags in a new div tag. */
function boxJSTSCouples() {
const tsTags = document.querySelectorAll(".language-ts");
tsTags.forEach(function (tsTag) {
const nextSibling = getNextSibling(tsTag, "js");
const previousSibling = getPreviousSibling(tsTag, "js");
if (nextSibling && previousSibling) {
// three subsequent code blocks, not clear what belongs to what
return;
} else if (!nextSibling && !previousSibling) {
// no direct sibling code block with different language
return;
} else if (nextSibling && getNextSibling(nextSibling)) {
// three subsequent code blocks, not clear what belongs to what
return;
} else if (previousSibling && getPreviousSibling(previousSibling)) {
// three subsequent code blocks, not clear what belongs to what
return;
}
const jsTag = nextSibling || previousSibling;
// we have two direct sibling code blocks with different languages; wrap them in a new div tag with nice switch button
const wrapper = document.createElement("div");
wrapper.classList.add("code-couple");
const tsButton = document.createElement("button");
const jsButton = document.createElement("button");
tsButton.classList.add("code-couple-button");
tsButton.classList.add("code-couple-button-ts");
tsButton.textContent = "TypeScript";
tsButton.addEventListener("click", function () {
// TODO: lots of redundant code to the one below
switchCodeCouple(wrapper, "ts");
});
wrapper.appendChild(tsButton);
jsButton.classList.add("code-couple-button");
jsButton.classList.add("code-couple-button-js");
jsButton.textContent = "JavaScript";
jsButton.addEventListener("click", function () {
switchCodeCouple(wrapper, "js");
});
wrapper.appendChild(jsButton);
tsTag.parentNode.insertBefore(wrapper, tsTag); // do this before tsTag is moved inside the wrapper
const wrapperContainer = document.createElement("div");
wrapperContainer.classList.add("code-couple-container");
wrapperContainer.appendChild(tsTag);
wrapperContainer.appendChild(jsTag);
wrapper.appendChild(wrapperContainer);
});
}
function switchCodeCouple(wrapper, lang) {
const tsTag = wrapper.querySelector(".language-ts");
const jsTag = wrapper.querySelector(".language-js");
const tsButton = wrapper.querySelector(".code-couple-button-ts");
const jsButton = wrapper.querySelector(".code-couple-button-js");
tsTag.style.display = lang === "ts" ? "block" : "none";
jsTag.style.display = lang === "js" ? "block" : "none";
tsButton.classList.toggle("code-couple-button-active", lang === "ts");
tsButton.classList.toggle("code-couple-button-inactive", lang === "js");
jsButton.classList.toggle("code-couple-button-active", lang === "js");
jsButton.classList.toggle("code-couple-button-inactive", lang === "ts");
wrapper.dataset.activeLang = lang;
}
function resetCodeCoupleButtons() {
const buttons = document.querySelectorAll(".code-couple-button");
buttons.forEach((button) => {
button.classList.remove(
"code-couple-button-active",
"code-couple-button-inactive"
);
});
}
function getPreviousSibling(tag, lang) {
const previousSibling =
tag.previousElementSibling &&
(tag.previousElementSibling.classList.contains("highlighter-rouge") ||
tag.nextElementSibling.classList.contains("hljs"))
? tag.previousElementSibling
: null;
if (
!lang ||
(previousSibling &&
(previousSibling.classList.contains("language-" + lang) ||
previousSibling.classList.contains("highlight-source-" + lang)))
) {
// success if lang does not matter or lang is as requested
return previousSibling;
}
return null;
}
function getNextSibling(tag, lang) {
const nextSibling =
tag.nextElementSibling &&
(tag.nextElementSibling.classList.contains("highlighter-rouge") ||
tag.nextElementSibling.classList.contains("hljs"))
? tag.nextElementSibling
: null;
if (
!lang ||
(nextSibling &&
(nextSibling.classList.contains("language-" + lang) ||
nextSibling.classList.contains("highlight-source-" + lang)))
) {
// success if lang does not matter or lang is as requested
return nextSibling;
}
return null;
}
/**
* This function finds all <details> tags with either the CSS class "ts-only" or "js-only" and:
* 1. removes their <summary> tag
* 2. replaces the <details> tag with a <section> tagr
*/
function replaceDetailSections() {
const detailTags = document.querySelectorAll(
"details.ts-only, details.js-only"
);
detailTags.forEach(function (detailTag) {
// create a new section tag before the detail tag
const sectionTag = document.createElement("section");
detailTag.parentNode.insertBefore(sectionTag, detailTag);
// copy over the either ts-only or js-only class to the new section tag
const lang = detailTag.classList.contains("ts-only") ? "ts" : "js";
sectionTag.classList.add(lang + "-only");
// move all children of the detail tag to the new section tag, except the <summary> tag
const children = Array.from(detailTag.children);
children.forEach(function (child) {
if (child.tagName.toLocaleUpperCase() === "SUMMARY") {
return;
}
// only add the child if it has content
if (child.innerHTML.trim()) {
sectionTag.appendChild(child);
}
});
// remove the detail tag
detailTag.parentNode.removeChild(detailTag);
});
}
function replaceFileExtensions(lang) {
const replacement =
"<span class='ts-only'>.ts</span><span class='js-only'>.js</span>";
// select all text nodes in the body
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
let node = walker.nextNode();
// iterate over each text node
while (node) {
let nextNode = walker.nextNode();
if (node.nodeValue.includes(".?s")) {
const temp = document.createElement("div");
temp.innerHTML = node.nodeValue.replace(/\.\?s/g, replacement);
const fragment = document.createDocumentFragment();
while (temp.firstChild) {
fragment.appendChild(temp.firstChild);
}
node.parentNode.replaceChild(fragment, node);
} else if (node.nodeValue.includes(".\\?s")) {
node.nodeValue = node.nodeValue.replace(/\.\\\?s/g, ".?s");
}
node = nextNode;
}
}
function updatePrevNextLinks(lang) {
const links = document.querySelectorAll('a[href$="README.md"], a[href$="/"], a[href$="README.md?lang=js"], a[href$="/?lang=js"]');
links.forEach(link => {
// Only handle relative links (not starting with http://, https://, or //)
if (/^(https?:)?\/\//.test(link.getAttribute("href"))) {
return;
}
let url = new URL(link.href, window.location.origin);
// Remove any existing lang param
url.searchParams.delete("lang");
if (lang === "js") {
url.searchParams.set("lang", "js");
}
link.href = url.pathname + url.search + url.hash;
});
}
// dynamic overall language switching
function addLanguageSwitchButtons() {
const buttonContainer = document.createElement("div");
buttonContainer.classList.add("language-switch-container");
const tsButton = createLanguageButton("TS", "ts");
const jsButton = createLanguageButton("JS", "js");
buttonContainer.appendChild(tsButton);
buttonContainer.appendChild(jsButton);
document.body.appendChild(buttonContainer);
}
function createLanguageButton(text, lang) {
const button = document.createElement("button");
button.textContent = text;
button.classList.add("language-switch-button");
button.classList.add("lang-" + lang);
button.addEventListener("click", () => switchLanguage(lang));
return button;
}
function switchLanguage(newLang) {
const lang = initializeLanguage(newLang);
replaceFileExtensions(lang);
resetCodeCoupleButtons();
updateAllCodeCouples(lang);
updatePrevNextLinks(lang);
}
function updateAllCodeCouples(globalLang) {
const codeCouples = document.querySelectorAll(".code-couple");
codeCouples.forEach((couple) => {
switchCodeCouple(couple, globalLang);
});
}
// initialization on startup
document.addEventListener("DOMContentLoaded", (event) => {
setTimeout(() => {
const lang = initializeLanguage();
replaceDetailSections();
boxJSTSCouples(); // should happen after replaceDetailSections, so all couples are recognized
replaceFileExtensions(lang);
addLanguageSwitchButtons();
updateAllCodeCouples(lang);
updatePrevNextLinks(lang);
});
});