mirror of
https://github.com/expressjs/expressjs.com.git
synced 2026-02-21 19:41:33 +00:00
chore: ♻️ remove jquery (#1964)
* add scroll-to-top and scroll class toggle with vanilla JS * add language detection * add i18n * Fix bug: there was no class hidden in CSS. Notice was hidden by hide(). Now HTML hidden attribute is used to hide notice using CSS. This improves A11y. * fix flashing of i18n notice and improve a11y * add menu highlighting on scroll * ♻️ remove jquery * defer app.js Co-authored-by: Sebastian Beltran <bjohansebas@gmail.com> * remove DOMContentLoaded and fix root margin to 25% --------- Co-authored-by: bjohansebas <103585995+bjohansebas@users.noreply.github.com>
This commit is contained in:
@@ -64,8 +64,7 @@
|
||||
{% else %}
|
||||
<meta property="twitter:image" content="https://expressjs.com/images/og.png">
|
||||
{% endif %}
|
||||
<script data-cfasync="false" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
<script data-cfasync="false" src="/js/app.js"></script>
|
||||
<script data-cfasync="false" defer src="/js/app.js"></script>
|
||||
<script data-cfasync="false" defer src="/js/menu.js"></script>
|
||||
<script data-cfasync="false" defer src="/js/copycode.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
{% assign notice_link_text = site.data.en.general.i18n_notice_link_text %}
|
||||
{% endif %}
|
||||
<p>{{ notice }} <a href="{{ page.url | replace: lang_path, '/en/' }}">{{ notice_link_text}}</a>.</p>
|
||||
<div id="close-i18n-notice-box" title="Close">✖</div>
|
||||
<div id="close-i18n-notice-box" title="Close" role="button" aria-label="Close notice" tabindex="0">✖</div>
|
||||
@@ -14,8 +14,8 @@
|
||||
<div id="overlay"></div>
|
||||
|
||||
{% if page.lang != 'en' %}
|
||||
<div id="i18n-notice-box" class="doc-notice">
|
||||
{% include i18n-notice.html %}
|
||||
<div id="i18n-notice-box" class="doc-notice" hidden>
|
||||
{% include i18n-notice.html %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div id="overlay"></div>
|
||||
|
||||
{% if page.lang != 'en' %}
|
||||
<div id="i18n-notice-box" class="doc-notice">
|
||||
<div id="i18n-notice-box" class="doc-notice" hidden>
|
||||
{% include i18n-notice.html %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -474,7 +474,7 @@ pre:has(code) button.failed {
|
||||
}
|
||||
}
|
||||
|
||||
/* top button */
|
||||
/* scroll to top button */
|
||||
|
||||
.scroll #top {
|
||||
opacity: .5;
|
||||
@@ -611,6 +611,7 @@ table ul {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* i18n box */
|
||||
.doc-notice {
|
||||
padding-block: 1rem;
|
||||
padding-inline: 2.5rem;
|
||||
@@ -618,6 +619,11 @@ table ul {
|
||||
border-radius: 0 6px 6px 0;
|
||||
background: var(--notice-bg);
|
||||
border-left: 3px solid var(--notice-accent);
|
||||
margin-inline: auto;
|
||||
margin-block-start: 2rem;
|
||||
position: relative;
|
||||
grid-area: i18n;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.doc-notice a{
|
||||
@@ -625,6 +631,10 @@ table ul {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.doc-notice[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.doc-info {
|
||||
background: var(--info-bg);
|
||||
border-left: 3px solid var(--info-accent);
|
||||
@@ -645,13 +655,6 @@ table ul {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#i18n-notice-box {
|
||||
margin-inline: auto;
|
||||
margin-block-start: 2rem;
|
||||
position: relative;
|
||||
grid-area: i18n;
|
||||
}
|
||||
|
||||
#close-i18n-notice-box {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
|
||||
210
js/app.js
210
js/app.js
@@ -1,122 +1,124 @@
|
||||
$(function(){
|
||||
var doc = $(document);
|
||||
const languageElement = document.getElementById('languageData');
|
||||
const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : [];
|
||||
const langDisplay = document.getElementById('current-lang');
|
||||
const i18nMsgBox = document.getElementById("i18n-notice-box");
|
||||
const scrollToTopBtn = document.getElementById("top");
|
||||
|
||||
// top link
|
||||
$('#top').click(function(e){
|
||||
$('html, body').animate({scrollTop : 0}, 500);
|
||||
return false;
|
||||
});
|
||||
// display current language in language picker component
|
||||
if (langDisplay) {
|
||||
const currentLanguage = window.location.pathname.split('/')[1];
|
||||
const matchedLang = languagesData.find(lang => lang.code === currentLanguage);
|
||||
langDisplay.textContent = matchedLang ? matchedLang.name : 'English';
|
||||
}
|
||||
|
||||
// scrolling links
|
||||
var added;
|
||||
doc.scroll(function(e){
|
||||
if (doc.scrollTop() > 5) {
|
||||
if (added) return;
|
||||
added = true;
|
||||
$('body').addClass('scroll');
|
||||
} else {
|
||||
$('body').removeClass('scroll');
|
||||
added = false;
|
||||
}
|
||||
})
|
||||
|
||||
// menu bar
|
||||
|
||||
var headings = $('h2, h3').map(function(i, el){
|
||||
return {
|
||||
top: $(el).offset().top - 200,
|
||||
id: el.id
|
||||
}
|
||||
});
|
||||
|
||||
function closest() {
|
||||
var h;
|
||||
var top = $(window).scrollTop();
|
||||
var i = headings.length;
|
||||
while (i--) {
|
||||
h = headings[i];
|
||||
if (top >= h.top) return h;
|
||||
}
|
||||
}
|
||||
|
||||
var currentApiPrefix;
|
||||
var parentMenuSelector;
|
||||
var lastApiPrefix;
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
const languageElement = document.getElementById('languageData');
|
||||
const languagesData = languageElement ? JSON.parse(languageElement.dataset.languages) : [];
|
||||
|
||||
const langDisplay = document.getElementById('current-lang');
|
||||
|
||||
if (langDisplay) {
|
||||
const currentLanguage = window.location.pathname.split('/')[1];
|
||||
const matchedLang = languagesData.find(lang => lang.code === currentLanguage);
|
||||
langDisplay.textContent = matchedLang ? matchedLang.name : 'English';
|
||||
}
|
||||
}
|
||||
|
||||
$(document).scroll(function() {
|
||||
var h = closest();
|
||||
if (!h) return;
|
||||
|
||||
currentApiPrefix = h.id.split('.')[0];
|
||||
parentMenuSelector = '#'+ currentApiPrefix + '-menu';
|
||||
|
||||
$(parentMenuSelector).addClass('active');
|
||||
|
||||
if (lastApiPrefix && (lastApiPrefix != currentApiPrefix)) {
|
||||
$('#'+ lastApiPrefix + '-menu').removeClass('active');
|
||||
}
|
||||
|
||||
$('#menu li a').removeClass('active');
|
||||
|
||||
var a = $('a[href="#' + h.id + '"]');
|
||||
a.addClass('active');
|
||||
|
||||
lastApiPrefix = currentApiPrefix.split('.')[0];
|
||||
})
|
||||
|
||||
// i18n notice
|
||||
if (readCookie('i18nClose')) {
|
||||
$('#i18n-notice-box').hide();
|
||||
$("#i18n-notice-box").addClass("hidden");
|
||||
}
|
||||
else {
|
||||
$('#close-i18n-notice-box').on('click', function () {
|
||||
$('#i18n-notice-box').hide();
|
||||
$("#i18n-notice-box").addClass("hidden");
|
||||
createCookie('i18nClose', 1);
|
||||
// scroll to top of the page
|
||||
if (scrollToTopBtn) {
|
||||
scrollToTopBtn.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth"
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// add/remove class 'scroll' on scroll by 5px
|
||||
const scrollTarget = document.querySelector('.logo-container');
|
||||
const scrollObserver = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (!entry.isIntersecting) {
|
||||
document.body.classList.add('scroll');
|
||||
} else {
|
||||
document.body.classList.remove('scroll');
|
||||
}
|
||||
},
|
||||
{
|
||||
root: null,
|
||||
threshold: 0,
|
||||
rootMargin: '0px 0px 0px 0px'
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (scrollTarget) scrollObserver.observe(scrollTarget);
|
||||
|
||||
// heighlight current Menu on scroll
|
||||
const headings = Array.from(document.querySelectorAll("h2, h3"));
|
||||
const menuLinks = document.querySelectorAll("#menu li a");
|
||||
|
||||
const observerOptions = {
|
||||
root: null,
|
||||
rootMargin: "-10% 0px -65% 0px",
|
||||
threshold: 1,
|
||||
};
|
||||
|
||||
const menuObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const currentApiPrefix = entry.target.id.split(".")[0];
|
||||
const parentMenuSelector = `#${currentApiPrefix}-menu`;
|
||||
const parentMenuEl = document.querySelector(parentMenuSelector);
|
||||
|
||||
// open submenu on scroll
|
||||
if (parentMenuEl) parentMenuEl.classList.add("active");
|
||||
|
||||
// Remove active class from last menu item
|
||||
const lastActiveMenu = document.querySelector(".active[id$='-menu']");
|
||||
if (lastActiveMenu && lastActiveMenu.id !== parentMenuEl.id) {
|
||||
lastActiveMenu.classList.remove("active");
|
||||
}
|
||||
|
||||
// Update active link
|
||||
menuLinks.forEach((link) => link.classList.remove("active"));
|
||||
const activeLink = document.querySelector(`a[href="#${entry.target.id}"]`);
|
||||
if (activeLink && !activeLink.classList.contains("active")) activeLink.classList.add("active");
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
headings.forEach((heading) => menuObserver.observe(heading));
|
||||
|
||||
// i18n message box : this box appears hidden for all page.lang != 'en'
|
||||
const isI18nCookie = readCookie('i18nClose');
|
||||
if (i18nMsgBox && !isI18nCookie) {
|
||||
const closeI18nBtn = document.getElementById("close-i18n-notice-box");
|
||||
// show notice box
|
||||
i18nMsgBox.hidden = false;
|
||||
// close notice box
|
||||
if (closeI18nBtn) {
|
||||
closeI18nBtn.addEventListener("click", () => {
|
||||
// hide notice
|
||||
i18nMsgBox.hidden = true;
|
||||
// set session cookie
|
||||
createCookie('i18nClose', 1);
|
||||
});
|
||||
|
||||
// keyboard a11y
|
||||
closeI18nBtn.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
closeI18nBtn.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function createCookie(name, value, days) {
|
||||
var expires;
|
||||
|
||||
let expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = "; expires=" + date.toGMTString();
|
||||
} else {
|
||||
expires = "";
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 864e5));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = encodeURIComponent(name) + "=" + encodeURIComponent(value) + expires + "; path=/";
|
||||
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}${expires}; path=/; SameSite=Lax; Secure`;
|
||||
}
|
||||
|
||||
function readCookie(name) {
|
||||
var nameEQ = encodeURIComponent(name) + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
const nameEQ = encodeURIComponent(name) + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
||||
if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function eraseCookie(name) {
|
||||
createCookie(name, "", -1);
|
||||
}
|
||||
Reference in New Issue
Block a user