Skip to content

Commit 313fc74

Browse files
committed
Introduce dev-server for local testing of GH pages
1 parent b170340 commit 313fc74

File tree

15 files changed

+2163
-2710
lines changed

15 files changed

+2163
-2710
lines changed

_includes/head-custom.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<link rel="stylesheet" href="{{ 'assets/css/custom.css' | relative_url }}">
2+
<script src="{{ 'assets/js/custom.js' | relative_url }}"></script>

assets/css/custom.css

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* hide content in the non-current language */
2+
.page-language-ts .js-only,
3+
.page-language-ts .code-couple .language-js.highlighter-rouge
4+
{
5+
display: none;
6+
}
7+
8+
.page-language-js .ts-only,
9+
.page-language-js .code-couple .language-ts.highlighter-rouge
10+
{
11+
display: none;
12+
}
13+
14+
15+
/* generic... maybe helpful... */
16+
.hidden {
17+
display: none;
18+
}
19+
20+
21+
/* tabbed switchable language area */
22+
.code-couple-button {
23+
margin-top: 8px;
24+
padding: 10px 20px;
25+
border: 1px solid #e1e4e8;
26+
border-bottom-width: 0;
27+
border-radius: 16px 16px 0 0;
28+
cursor: pointer;
29+
}
30+
31+
/* active */
32+
.code-couple-button-active {
33+
color: black;
34+
background-color: white;
35+
font-weight: bold;
36+
}
37+
38+
/* inactive */
39+
.code-couple-button-inactive {
40+
color: #586069;
41+
background-color: #f6f8fa;
42+
}
43+
44+
/* default state based on page language */
45+
.page-language-ts .code-couple-button-ts:not(.code-couple-button-active):not(.code-couple-button-inactive),
46+
.page-language-js .code-couple-button-js:not(.code-couple-button-active):not(.code-couple-button-inactive) {
47+
color: black;
48+
background-color: white;
49+
font-weight: bold;
50+
}
51+
52+
.page-language-ts .code-couple-button-js:not(.code-couple-button-active):not(.code-couple-button-inactive),
53+
.page-language-js .code-couple-button-ts:not(.code-couple-button-active):not(.code-couple-button-inactive) {
54+
color: #586069;
55+
background-color: #f6f8fa;
56+
}
57+
58+
.code-couple-container {
59+
border: 1px solid #e1e4e8;
60+
}
61+
62+
63+
/* overall language switch buttons */
64+
65+
.language-switch-container {
66+
position: fixed;
67+
top: 10px;
68+
right: 10px;
69+
display: flex;
70+
gap: 5px;
71+
z-index: 1000;
72+
}
73+
74+
.language-switch-button {
75+
background-color: rgba(0, 0, 0, 0.5);
76+
color: white;
77+
border: none;
78+
padding: 5px 10px;
79+
border-radius: 5px;
80+
cursor: pointer;
81+
transition: opacity 0.3s, background-color 0.3s;
82+
opacity: 0.7;
83+
}
84+
85+
.language-switch-button:hover {
86+
opacity: 1;
87+
background-color: rgba(0, 0, 0, 0.8);
88+
}

assets/js/custom.js

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
function getUrlParameter(name) {
2+
name = name.replace(/[\[\]]/g, '\\$&');
3+
let regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
4+
let results = regex.exec(window.location.href);
5+
if (!results) return null;
6+
if (!results[2]) return '';
7+
return decodeURIComponent(results[2].replace(/\+/g, ' '));
8+
}
9+
10+
function initializeLanguage(forcedLang = null) {
11+
let lang = forcedLang || getUrlParameter("lang");
12+
if (lang !== "js") {
13+
lang = "ts";
14+
}
15+
16+
// set top-level CSS class based on current language
17+
document.body.classList.remove("page-language-js", "page-language-ts"); // for later switching
18+
document.body.classList.add("page-language-" + lang);
19+
20+
return lang;
21+
}
22+
23+
/* this function finds all tags which have both CSS classes "language-ts" and "highlighter-rouge"
24+
and have exactly one direct neighbor sibling tag, which has the classes "language-ts" and "highlighter-rouge".
25+
It then wraps both tags in a new div tag. */
26+
function boxJSTSCouples() {
27+
const tsTags = document.querySelectorAll(".language-ts");
28+
tsTags.forEach(function (tsTag) {
29+
const nextSibling = getNextSibling(tsTag, "js");
30+
const previousSibling = getPreviousSibling(tsTag, "js");
31+
if (nextSibling && previousSibling) { // three subsequent code blocks, not clear what belongs to what
32+
return;
33+
} else if (!nextSibling && !previousSibling) { // no direct sibling code block with different language
34+
return;
35+
} else if (nextSibling && getNextSibling(nextSibling)) { // three subsequent code blocks, not clear what belongs to what
36+
return;
37+
} else if (previousSibling && getPreviousSibling(previousSibling)) { // three subsequent code blocks, not clear what belongs to what
38+
return;
39+
}
40+
const jsTag = nextSibling || previousSibling;
41+
42+
// we have two direct sibling code blocks with different languages; wrap them in a new div tag with nice switch button
43+
const wrapper = document.createElement("div");
44+
wrapper.classList.add("code-couple");
45+
46+
const tsButton = document.createElement("button");
47+
const jsButton = document.createElement("button");
48+
tsButton.classList.add("code-couple-button");
49+
tsButton.classList.add("code-couple-button-ts");
50+
tsButton.textContent = "TypeScript";
51+
tsButton.addEventListener("click", function () { // TODO: lots of redundant code to the one below
52+
switchCodeCouple(wrapper, 'ts');
53+
});
54+
wrapper.appendChild(tsButton);
55+
56+
jsButton.classList.add("code-couple-button");
57+
jsButton.classList.add("code-couple-button-js");
58+
jsButton.textContent = "JavaScript";
59+
jsButton.addEventListener("click", function () {
60+
switchCodeCouple(wrapper, 'js');
61+
});
62+
wrapper.appendChild(jsButton);
63+
tsTag.parentNode.insertBefore(wrapper, tsTag); // do this before tsTag is moved inside the wrapper
64+
65+
const wrapperContainer = document.createElement("div");
66+
wrapperContainer.classList.add("code-couple-container");
67+
wrapperContainer.appendChild(tsTag);
68+
wrapperContainer.appendChild(jsTag);
69+
wrapper.appendChild(wrapperContainer);
70+
});
71+
}
72+
73+
function switchCodeCouple(wrapper, lang) {
74+
const tsTag = wrapper.querySelector('.language-ts');
75+
const jsTag = wrapper.querySelector('.language-js');
76+
const tsButton = wrapper.querySelector('.code-couple-button-ts');
77+
const jsButton = wrapper.querySelector('.code-couple-button-js');
78+
79+
tsTag.style.display = lang === 'ts' ? 'block' : 'none';
80+
jsTag.style.display = lang === 'js' ? 'block' : 'none';
81+
82+
tsButton.classList.toggle('code-couple-button-active', lang === 'ts');
83+
tsButton.classList.toggle('code-couple-button-inactive', lang === 'js');
84+
jsButton.classList.toggle('code-couple-button-active', lang === 'js');
85+
jsButton.classList.toggle('code-couple-button-inactive', lang === 'ts');
86+
87+
wrapper.dataset.activeLang = lang;
88+
}
89+
90+
function resetCodeCoupleButtons() {
91+
const buttons = document.querySelectorAll('.code-couple-button');
92+
buttons.forEach(button => {
93+
button.classList.remove('code-couple-button-active', 'code-couple-button-inactive');
94+
});
95+
}
96+
97+
98+
function getPreviousSibling(tag, lang) {
99+
const previousSibling = tag.previousElementSibling && (tag.previousElementSibling.classList.contains("highlighter-rouge") || tag.nextElementSibling.classList.contains("hljs")) ? tag.previousElementSibling : null;
100+
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
101+
return previousSibling;
102+
}
103+
return null;
104+
}
105+
106+
function getNextSibling(tag, lang) {
107+
const nextSibling = tag.nextElementSibling && (tag.nextElementSibling.classList.contains("highlighter-rouge") || tag.nextElementSibling.classList.contains("hljs")) ? tag.nextElementSibling : null;
108+
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
109+
return nextSibling;
110+
}
111+
return null;
112+
}
113+
114+
/**
115+
* This function finds all <details> tags with either the CSS class "ts-only" or "js-only" and:
116+
* 1. removes their <summary> tag
117+
* 2. replaces the <details> tag with a <section> tagr
118+
*/
119+
function replaceDetailSections() {
120+
const detailTags = document.querySelectorAll("details.ts-only, details.js-only");
121+
detailTags.forEach(function (detailTag) {
122+
// create a new section tag before the detail tag
123+
const sectionTag = document.createElement("section");
124+
detailTag.parentNode.insertBefore(sectionTag, detailTag);
125+
126+
// copy over the either ts-only or js-only class to the new section tag
127+
const lang = detailTag.classList.contains("ts-only") ? "ts" : "js";
128+
sectionTag.classList.add(lang + "-only");
129+
130+
// move all children of the detail tag to the new section tag, except the <summary> tag
131+
const children = Array.from(detailTag.children);
132+
children.forEach(function (child) {
133+
if (child.tagName.toLocaleUpperCase() === "SUMMARY") {
134+
return;
135+
}
136+
sectionTag.appendChild(child);
137+
});
138+
139+
// remove the detail tag
140+
detailTag.parentNode.removeChild(detailTag);
141+
});
142+
}
143+
144+
function replaceFileExtensions(lang) {
145+
const replacement = "<span class='ts-only'>.ts</span><span class='js-only'>.js</span>";
146+
// select all text nodes in the body
147+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
148+
let node;
149+
150+
// iterate over each text node
151+
while (node = walker.nextNode()) {
152+
if (node.nodeValue.includes('.?s')) {
153+
const temp = document.createElement('div');
154+
temp.innerHTML = node.nodeValue.replace(/\.\?s/g, replacement);
155+
156+
const fragment = document.createDocumentFragment();
157+
while (temp.firstChild) {
158+
fragment.appendChild(temp.firstChild);
159+
}
160+
161+
node.parentNode.replaceChild(fragment, node);
162+
}
163+
}
164+
}
165+
166+
167+
// dynamic overall language switching
168+
169+
function addLanguageSwitchButtons() {
170+
const buttonContainer = document.createElement('div');
171+
buttonContainer.classList.add('language-switch-container');
172+
173+
const jsButton = createLanguageButton('JS', 'js');
174+
const tsButton = createLanguageButton('TS', 'ts');
175+
176+
buttonContainer.appendChild(jsButton);
177+
buttonContainer.appendChild(tsButton);
178+
document.body.appendChild(buttonContainer);
179+
}
180+
181+
function createLanguageButton(text, lang) {
182+
const button = document.createElement('button');
183+
button.textContent = text;
184+
button.classList.add('language-switch-button');
185+
button.addEventListener('click', () => switchLanguage(lang));
186+
return button;
187+
}
188+
189+
function switchLanguage(newLang) {
190+
const lang = initializeLanguage(newLang);
191+
replaceFileExtensions(lang);
192+
resetCodeCoupleButtons();
193+
updateAllCodeCouples(lang);
194+
}
195+
196+
function updateAllCodeCouples(globalLang) {
197+
const codeCouples = document.querySelectorAll('.code-couple');
198+
codeCouples.forEach(couple => {
199+
switchCodeCouple(couple, globalLang);
200+
});
201+
}
202+
203+
204+
// initialization on startup
205+
206+
document.addEventListener("DOMContentLoaded", (event) => {
207+
const lang = initializeLanguage();
208+
replaceDetailSections();
209+
boxJSTSCouples(); // should happen after replaceDetailSections, so all couples are recognized
210+
replaceFileExtensions(lang);
211+
addLanguageSwitchButtons();
212+
updateAllCodeCouples(lang);
213+
});

0 commit comments

Comments
 (0)