diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..ca441ef1b --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.min.* diff --git a/.prettierrc.toml b/.prettierrc.toml new file mode 100644 index 000000000..9e47a918f --- /dev/null +++ b/.prettierrc.toml @@ -0,0 +1,11 @@ +semi = true +trailingComma = "es5" +singleQuote = true +printWidth = 88 +tabWidth = 4 +useTabs = false +arrowParens = "always" +bracketSpacing = true +jsxBracketSameLine = false +jsxSingleQuote = true +endOfLine = "lf" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a1d2916f..713a48da4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ If you're not sure how to contribute or need help with something, please don't h While we don't enforce a strict coding style, we appreciate it when contributions follow the existing code style of the project (e.g. indenting with 4 spaces). This makes the codebase easier to read and maintain. If you are making significant changes or additions, please try to maintain consistency with the current coding patterns and idioms. -For JavaScript files, you can use [Biome](https://biomejs.dev/) to format your code. +For JavaScript files, we use [Prettier](https://prettier.io/) to format the code. The CSS properties are sorted following [Concentric-CSS](https://github.com/brandon-rhodes/Concentric-CSS). If you use VSCode, the [Sort CSS](https://marketplace.visualstudio.com/items?itemName=piyushsarkar.sort-css-properties) extension makes this super easy. diff --git a/static/js/copyCodeToClipboard.js b/static/js/copyCodeToClipboard.js index fde877677..e22debef6 100644 --- a/static/js/copyCodeToClipboard.js +++ b/static/js/copyCodeToClipboard.js @@ -3,45 +3,45 @@ const initCopyText = document.getElementById('copy-init').textContent; const changeIcon = (copyDiv, className) => { copyDiv.classList.add(className); - copyDiv.setAttribute("aria-label", copiedText); + copyDiv.setAttribute('aria-label', copiedText); setTimeout(() => { copyDiv.classList.remove(className); - copyDiv.setAttribute("aria-label", initCopyText); + copyDiv.setAttribute('aria-label', initCopyText); }, 2500); }; const addCopyEventListenerToDiv = (copyDiv, block) => { - copyDiv.addEventListener("click", () => copyCodeAndChangeIcon(copyDiv, block)); + copyDiv.addEventListener('click', () => copyCodeAndChangeIcon(copyDiv, block)); }; const copyCodeAndChangeIcon = async (copyDiv, block) => { - const code = block.querySelector('table') ? getTableCode(block) : getNonTableCode(block); + const code = block.querySelector('table') + ? getTableCode(block) + : getNonTableCode(block); try { await navigator.clipboard.writeText(code); - changeIcon(copyDiv, "checked"); + changeIcon(copyDiv, 'checked'); } catch (error) { - changeIcon(copyDiv, "error"); + changeIcon(copyDiv, 'error'); } }; const getNonTableCode = (block) => { - return [...block.querySelectorAll('code')] - .map(code => code.textContent) - .join(''); + return [...block.querySelectorAll('code')].map((code) => code.textContent).join(''); }; const getTableCode = (block) => { return [...block.querySelectorAll('tr')] - .map(row => row.querySelector('td:last-child')?.innerText ?? '') + .map((row) => row.querySelector('td:last-child')?.innerText ?? '') .join(''); }; -document.querySelectorAll("pre").forEach((block) => { - const copyDiv = document.createElement("div"); - copyDiv.setAttribute("role", "button"); - copyDiv.setAttribute("aria-label", initCopyText); - copyDiv.setAttribute("title", initCopyText); - copyDiv.className = "copy-code"; +document.querySelectorAll('pre').forEach((block) => { + const copyDiv = document.createElement('div'); + copyDiv.setAttribute('role', 'button'); + copyDiv.setAttribute('aria-label', initCopyText); + copyDiv.setAttribute('title', initCopyText); + copyDiv.className = 'copy-code'; block.prepend(copyDiv); addCopyEventListenerToDiv(copyDiv, block); }); diff --git a/static/js/decodeMail.js b/static/js/decodeMail.js index 67519918d..f26ac6869 100644 --- a/static/js/decodeMail.js +++ b/static/js/decodeMail.js @@ -11,16 +11,16 @@ const byteString = atob(encodedString); // Convert byteString to an array of char codes. - const charCodes = [...byteString].map(char => char.charCodeAt(0)); + const charCodes = [...byteString].map((char) => char.charCodeAt(0)); // Use TypedArray.prototype.set() to copy the char codes into a Uint8Array. const bytes = new Uint8Array(charCodes.length); bytes.set(charCodes); - const decoder = new TextDecoder("utf-8"); + const decoder = new TextDecoder('utf-8'); return decoder.decode(bytes); } catch (e) { - console.error("Failed to decode Base64 string: ", e); + console.error('Failed to decode Base64 string: ', e); return null; } } diff --git a/static/js/giscus.js b/static/js/giscus.js index 13ed38971..1fbe837dc 100644 --- a/static/js/giscus.js +++ b/static/js/giscus.js @@ -55,7 +55,8 @@ function initGiscus() { } // Choose the correct theme based on the current theme of the document. - const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; + const currentTheme = + document.documentElement.getAttribute('data-theme') || 'light'; const selectedTheme = currentTheme === 'dark' ? darkTheme : lightTheme; script.setAttribute('data-theme', selectedTheme); @@ -69,7 +70,8 @@ function initGiscus() { // Listen for theme changes and update the Giscus theme when they occur. window.addEventListener('themeChanged', (event) => { - const selectedTheme = event.detail.theme === 'dark' ? darkTheme : lightTheme; + const selectedTheme = + event.detail.theme === 'dark' ? darkTheme : lightTheme; setGiscusTheme(selectedTheme); }); } diff --git a/static/js/hyvortalk.js b/static/js/hyvortalk.js index a003141f0..3f9959d92 100644 --- a/static/js/hyvortalk.js +++ b/static/js/hyvortalk.js @@ -25,7 +25,8 @@ function initHyvorTalk() { comments.setAttribute('page-author', pageAuthor); // Choose the correct theme based on the current theme of the document. - const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; + const currentTheme = + document.documentElement.getAttribute('data-theme') || 'light'; comments.setAttribute('colors', currentTheme); // Add the Hyvor Talk comments tag to the div. diff --git a/static/js/initializeTheme.js b/static/js/initializeTheme.js index 7d8a21412..95e754c1d 100644 --- a/static/js/initializeTheme.js +++ b/static/js/initializeTheme.js @@ -17,6 +17,9 @@ } else { // If no theme is found in local storage and no default theme is set, use user's system preference. const isSystemDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - document.documentElement.setAttribute('data-theme', isSystemDark ? 'dark' : 'light'); + document.documentElement.setAttribute( + 'data-theme', + isSystemDark ? 'dark' : 'light' + ); } })(); diff --git a/static/js/isso.js b/static/js/isso.js index 40974743c..4666604f9 100644 --- a/static/js/isso.js +++ b/static/js/isso.js @@ -4,13 +4,13 @@ function initIsso() { const commentsDiv = document.querySelector('.comments'); if (commentsDiv) { // Get the lazy-loading setting from the div. - const lazyLoading = commentsDiv.getAttribute('data-lazy-loading') === "true"; + const lazyLoading = commentsDiv.getAttribute('data-lazy-loading') === 'true'; // If lazy-loading is enabled, create an Intersection Observer and use it. if (lazyLoading) { - const observer = new IntersectionObserver(entries => { + const observer = new IntersectionObserver((entries) => { // Loop over the entries. - entries.forEach(entry => { + entries.forEach((entry) => { // If the element is in the viewport, initialize Isso. if (entry.isIntersecting) { loadIsso(commentsDiv); diff --git a/static/js/sortTable.js b/static/js/sortTable.js index a7fab495e..16e02e91c 100644 --- a/static/js/sortTable.js +++ b/static/js/sortTable.js @@ -1,6 +1,6 @@ // Select the table and table headers. -var table = document.querySelector("#sitemapTable"); -var headers = Array.from(table.querySelectorAll("th")); +var table = document.querySelector('#sitemapTable'); +var headers = Array.from(table.querySelectorAll('th')); // Create and append the live region for accessibility announcements. var liveRegion = document.createElement('div'); @@ -37,11 +37,11 @@ function initializeHeaders() { header.sortDirection = 'asc'; // Default sort direction. var sortAttribute = index === 0 ? 'ascending' : 'none'; header.setAttribute('aria-sort', sortAttribute); - header.addEventListener("click", function () { + header.addEventListener('click', function () { sortTable(index); }); - header.addEventListener("keydown", function (e) { - if (e.key === "Enter" || e.key === " ") { + header.addEventListener('keydown', function (e) { + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); sortTable(index); } @@ -51,14 +51,15 @@ function initializeHeaders() { function announceSort(header, direction) { var columnTitle = header.querySelector('.columntitle').textContent; - liveRegion.textContent = 'Column ' + columnTitle + ' is now sorted in ' + direction + ' order'; + liveRegion.textContent = + 'Column ' + columnTitle + ' is now sorted in ' + direction + ' order'; } function sortTable(index) { var header = headers[index]; var direction = header.sortDirection === 'asc' ? 'desc' : 'asc'; - var tbody = table.querySelector("tbody"); - var rows = Array.from(tbody.querySelectorAll("tr")); + var tbody = table.querySelector('tbody'); + var rows = Array.from(tbody.querySelectorAll('tr')); sortRows(rows, index, direction); refreshTableBody(tbody, rows); updateHeaderAttributes(header, direction); @@ -67,9 +68,11 @@ function sortTable(index) { function sortRows(rows, index, direction) { rows.sort(function (rowA, rowB) { - var cellA = rowA.querySelectorAll("td")[index].textContent; - var cellB = rowB.querySelectorAll("td")[index].textContent; - return direction === 'asc' ? cellA.localeCompare(cellB) : cellB.localeCompare(cellA); + var cellA = rowA.querySelectorAll('td')[index].textContent; + var cellB = rowB.querySelectorAll('td')[index].textContent; + return direction === 'asc' + ? cellA.localeCompare(cellB) + : cellB.localeCompare(cellA); }); } @@ -96,7 +99,10 @@ function updateHeaderAttributes(header, direction) { // Update screen reader text for sorting. function updateAnnounceText(header) { var span = header.querySelector('.visually-hidden'); - span.textContent = 'Click to sort in ' + (header.sortDirection === 'asc' ? 'descending' : 'ascending') + ' order'; + span.textContent = + 'Click to sort in ' + + (header.sortDirection === 'asc' ? 'descending' : 'ascending') + + ' order'; } // Add text for screen readers regarding sort order. diff --git a/static/js/themeSwitcher.js b/static/js/themeSwitcher.js index bc4e825f5..b5afa453c 100644 --- a/static/js/themeSwitcher.js +++ b/static/js/themeSwitcher.js @@ -1,30 +1,33 @@ // Get the theme switcher button elements. -const themeSwitcher = document.querySelector(".theme-switcher"); -const themeResetter = document.querySelector(".theme-resetter"); +const themeSwitcher = document.querySelector('.theme-switcher'); +const themeResetter = document.querySelector('.theme-resetter'); const defaultTheme = document.documentElement.getAttribute('data-default-theme'); function getSystemThemePreference() { - return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } // Determine the initial theme. -let currentTheme = localStorage.getItem("theme") || document.documentElement.getAttribute('data-theme') || getSystemThemePreference(); +let currentTheme = + localStorage.getItem('theme') || + document.documentElement.getAttribute('data-theme') || + getSystemThemePreference(); function setTheme(theme, saveToLocalStorage = false) { - document.documentElement.setAttribute("data-theme", theme); + document.documentElement.setAttribute('data-theme', theme); currentTheme = theme; - themeSwitcher.setAttribute("aria-pressed", theme === "dark"); + themeSwitcher.setAttribute('aria-pressed', theme === 'dark'); if (saveToLocalStorage) { - localStorage.setItem("theme", theme); - themeResetter.classList.add("has-custom-theme"); + localStorage.setItem('theme', theme); + themeResetter.classList.add('has-custom-theme'); } else { - localStorage.removeItem("theme"); - themeResetter.classList.remove("has-custom-theme"); + localStorage.removeItem('theme'); + themeResetter.classList.remove('has-custom-theme'); } // Dispatch a custom event for comment systems. - window.dispatchEvent(new CustomEvent("themeChanged", { detail: { theme } })); + window.dispatchEvent(new CustomEvent('themeChanged', { detail: { theme } })); } function resetTheme() { @@ -33,22 +36,24 @@ function resetTheme() { // Function to switch between dark and light themes. function switchTheme() { - setTheme(currentTheme === "dark" ? "light" : "dark", true); + setTheme(currentTheme === 'dark' ? 'light' : 'dark', true); } // Initialize the theme switcher button. -themeSwitcher.addEventListener("click", switchTheme); -themeResetter.addEventListener("click", resetTheme); +themeSwitcher.addEventListener('click', switchTheme); +themeResetter.addEventListener('click', resetTheme); // Update the theme based on system preference if necessary. if (!defaultTheme) { - window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", e => { - setTheme(e.matches ? "dark" : "light"); - }); + window + .matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', (e) => { + setTheme(e.matches ? 'dark' : 'light'); + }); } // Set initial ARIA attribute and custom theme class. -themeSwitcher.setAttribute("aria-pressed", currentTheme === "dark"); -if (localStorage.getItem("theme")) { - themeResetter.classList.add("has-custom-theme"); +themeSwitcher.setAttribute('aria-pressed', currentTheme === 'dark'); +if (localStorage.getItem('theme')) { + themeResetter.classList.add('has-custom-theme'); } diff --git a/static/js/utterances.js b/static/js/utterances.js index f652d3367..ddfa35a02 100644 --- a/static/js/utterances.js +++ b/static/js/utterances.js @@ -1,6 +1,6 @@ function setUtterancesTheme(newTheme) { // Get the frame with class "utterances-frame". - const frame = document.querySelector(".utterances-frame"); + const frame = document.querySelector('.utterances-frame'); if (frame) { // If the iframe exists, send a message to set the theme. @@ -34,7 +34,8 @@ function initUtterances() { script.setAttribute('label', label); // Set the initial theme. - const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; + const currentTheme = + document.documentElement.getAttribute('data-theme') || 'light'; const selectedTheme = currentTheme === 'dark' ? darkTheme : lightTheme; script.setAttribute('theme', selectedTheme); @@ -51,7 +52,8 @@ function initUtterances() { // Listen for themeChanged event to update the theme. window.addEventListener('themeChanged', (event) => { // Determine the new theme based on the event detail. - const selectedTheme = event.detail.theme === 'dark' ? darkTheme : lightTheme; + const selectedTheme = + event.detail.theme === 'dark' ? darkTheme : lightTheme; // Set the new theme. setUtterancesTheme(selectedTheme); });