From b5fa05a9d68a3fd4755865a236d2e82ee9701478 Mon Sep 17 00:00:00 2001 From: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Date: Sat, 29 Nov 2025 09:38:58 -0500 Subject: [PATCH] docs: Refactor and modernize projects.js Rewrote projects.js to use vanilla JavaScript instead of jQuery, modularized code into functions, improved error handling, and enhanced readability. The script now dynamically loads and displays LizardByte projects from Read the Docs, sorts them, and updates the table of contents. This refactor improves maintainability and performance. --- docs/source/_static/js/projects.js | 323 ++++++++++++++++++++--------- docs/source/conf.py | 1 - 2 files changed, 222 insertions(+), 102 deletions(-) diff --git a/docs/source/_static/js/projects.js b/docs/source/_static/js/projects.js index 0243deac..54a5d370 100644 --- a/docs/source/_static/js/projects.js +++ b/docs/source/_static/js/projects.js @@ -1,109 +1,230 @@ -// custom javascript to get a list of other LizardByte projects using Readthedocs +/** + * @file projects.js + * @description Dynamically loads and displays a list of LizardByte projects with documentation hosted on Read the Docs. + * This script creates a new section on the overview page with a sortable list of active and archived projects. + */ + +/** + * Creates and appends a projects section to the overview element. + * @returns {HTMLElement|null} The created section element, or null if overview doesn't exist. + */ +function createProjectsSection() { + const overview = document.getElementById("overview"); + if (!overview) { + return null; + } + + const section = document.createElement("section"); + section.className = "active"; + section.id = "projects"; + + overview.appendChild(section); + return section; +} + +/** + * Adds a "Projects" link to the "on this page" table of contents navigation. + */ +function addProjectsToToc() { + const tocTree = document.getElementsByClassName("toc-tree")[0]; + if (!tocTree) { + return; + } -// wait until ready -$(document).ready(function() { - // get element by id - let overview = document.getElementById("overview") + const tocTreeLists = tocTree.getElementsByTagName("ul"); + if (tocTreeLists.length < 2) { + return; + } - // create a new section - let section = document.createElement("section") - section.className = "active" - section.id = "projects" + const tocTreeList = tocTreeLists[1]; + const tocListItem = document.createElement("li"); + + const tocListItemLink = document.createElement("a"); + tocListItemLink.className = "reference internal"; + tocListItemLink.href = "#projects"; + tocListItemLink.textContent = "Projects"; + + tocListItem.appendChild(tocListItemLink); + tocTreeList.appendChild(tocListItem); +} + +/** + * Creates and appends the heading for the Projects section. + * @param {HTMLElement} section - The section element to append the heading to. + */ +function createProjectsHeading(section) { + const heading = document.createElement("h2"); + heading.textContent = "Projects"; + + const headingLink = document.createElement("a"); + headingLink.className = "headerlink"; + headingLink.href = "#projects"; + headingLink.title = "Permalink to this headline"; + headingLink.textContent = "#"; + + heading.appendChild(headingLink); + section.appendChild(heading); +} + +/** + * Creates and appends a description paragraph to the Projects section. + * @param {HTMLElement} section - The section element to append the paragraph to. + */ +function createProjectsDescription(section) { + const paragraph = document.createElement("p"); + paragraph.textContent = "Below is a list of our projects with documentation hosted on Read the Docs."; + section.appendChild(paragraph); +} + +/** + * Creates and appends an unordered list for projects. + * @param {HTMLElement} section - The section element to append the list to. + * @returns {HTMLElement} The created list element. + */ +function createProjectsList(section) { + const projectList = document.createElement("ul"); + projectList.className = "simple"; + section.appendChild(projectList); + return projectList; +} + +/** + * Parses project data and extracts relevant information. + * @param {Object} data - The raw project data from the API. + * @returns {Array} An array of project objects with name, url, and archived status. + */ +function parseProjectData(data) { + const projects = []; + + for (const key in data) { + if (!Object.hasOwn(data, key)) { + continue; + } - // add it to the overview - try { - overview.appendChild(section) + const projectData = data[key]; + + // Check if the project is archived + const archived = projectData.child?.tags?.includes("archived") || false; + + // Create a project object + const project = { + name: projectData.child?.name || "Unknown", + nameLower: (projectData.child?.name || "").toLowerCase(), + url: projectData.child?.urls?.documentation || "#", + archived: archived + }; + + projects.push(project); + } + + return projects; +} + +/** + * Sorts projects by name in ascending order. + * @param {Array} projects - The array of project objects to sort. + * @returns {Array} The sorted array of projects. + */ +function sortProjects(projects) { + return projects.sort((a, b) => { + return a.nameLower.localeCompare(b.nameLower); + }); +} + +/** + * Renders a project list item in the DOM. + * @param {HTMLElement} projectList - The list element to append the item to. + * @param {Object} project - The project object containing name, url, and archived status. + */ +function renderProjectItem(projectList, project) { + const listItem = document.createElement("li"); + + const link = document.createElement("a"); + link.href = project.url; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + link.textContent = project.archived ? `${project.name} (Archived)` : project.name; + + listItem.appendChild(link); + projectList.appendChild(listItem); +} + +/** + * Renders all projects in the list, with active projects first, then archived. + * @param {HTMLElement} projectList - The list element to render projects into. + * @param {Array} projects - The sorted array of project objects. + */ +function renderProjects(projectList, projects) { + // Render active projects first + for (const project of projects) { + if (!project.archived) { + renderProjectItem(projectList, project); + } } - catch { - return // not the right page + + // Then render archived projects + for (const project of projects) { + if (project.archived) { + renderProjectItem(projectList, project); + } } +} + +/** + * Fetches project data from the API endpoint. + * @param {HTMLElement} projectList - The list element to populate with projects. + * @returns {Promise} + */ +async function fetchAndRenderProjects(projectList) { + const apiUrl = "https://app.lizardbyte.dev/dashboard/readthedocs/subprojects/.github.json"; + + try { + const response = await fetch(apiUrl); - // add projects to the "on this page" table of contents - // get the first element with toc-tree class - let toc_tree = document.getElementsByClassName("toc-tree")[0] - // get the second ul element in the toc-tree - let toc_tree_list = toc_tree.getElementsByTagName("ul")[1] - // create a new list item - let toc_list_item = document.createElement("li") - toc_tree_list.appendChild(toc_list_item) - // create the link - let toc_list_item_link = document.createElement("a") - toc_list_item_link.className = "reference internal" - toc_list_item_link.href = "#projects" - toc_list_item_link.textContent = "Projects" - toc_list_item.appendChild(toc_list_item_link) - - // create a new h2 heading - let heading = document.createElement("h2") - heading.textContent = "Projects" - section.appendChild(heading) - - let heading_link = document.createElement("a") - heading_link.className = "headerlink" - heading_link.href = "#projects" - heading_link.title = "Permalink to this headline" - heading_link.textContent = "#" - heading.appendChild(heading_link) - - // create a new paragraph - let paragraph = document.createElement("p") - paragraph.textContent = "Below is a list of our projects with documentation hosted on Read the Docs." - section.appendChild(paragraph) - - // create a new unordered list - let project_list = document.createElement("ul") - project_list.className = "simple" - section.appendChild(project_list) - - // get project data using ajax - $.ajax({ - url: "https://app.lizardbyte.dev/dashboard/readthedocs/subprojects/.github.json", - dataType: "json", - success: function(data) { - // create a projects list - let projects = [] - - for (let i in data) { - // check if the project is archived - let archived = false - for (let tag in data[i]['child']['tags']) { - if (data[i]['child']['tags'][tag] === "archived") { - archived = true - } - } - - // create a new project dictionary - let project = { - 'name': data[i]['child']['name'], - 'name_lower': data[i]['child']['name'].toLowerCase(), - 'url': data[i]['child']['urls']['documentation'], - 'archived': archived - } - - // add the project to the list - projects.push(project) - } - - // sort the projects by name - let sorted_projects = projects.toSorted(rankingSorter('name_lower', 'name')).reverse() - - for (let a of [false, true]) { - for (let i in sorted_projects) { - if (sorted_projects[i]['archived'] === a) { - // create a new list item - let project_list_item = document.createElement("li") - project_list.appendChild(project_list_item) - - // create a new link - let project_list_item_link = document.createElement("a") - project_list_item_link.href = sorted_projects[i]['url'] - project_list_item_link.target = "_blank" - project_list_item_link.textContent = sorted_projects[i]['name'] + (a ? " (Archived)" : "") - project_list_item.appendChild(project_list_item_link) - } - } - } + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } - }) // end ajax -}) + const data = await response.json(); + const projects = parseProjectData(data); + const sortedProjects = sortProjects(projects); + + renderProjects(projectList, sortedProjects); + } catch (error) { + console.error("Error fetching projects:", error); + + // Display error message to user + const errorItem = document.createElement("li"); + errorItem.textContent = "Failed to load projects. Please try again later."; + errorItem.style.color = "red"; + projectList.appendChild(errorItem); + } +} + +/** + * Initializes the projects section on the page. + * This is the main entry point that orchestrates all the functionality. + */ +function initializeProjectsSection() { + const section = createProjectsSection(); + + // If we're not on the right page, exit early + if (!section) { + return; + } + + addProjectsToToc(); + createProjectsHeading(section); + createProjectsDescription(section); + + const projectList = createProjectsList(section); + fetchAndRenderProjects(projectList); +} + +// Initialize when DOM is fully loaded +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initializeProjectsSection); +} else { + // DOM is already loaded + initializeProjectsSection(); +} diff --git a/docs/source/conf.py b/docs/source/conf.py index 27b9a3c6..613f4c88 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -76,7 +76,6 @@ 'https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2025.326.11214/dist/crowdin-furo-css.css', ] html_js_files = [ - 'https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js', # jquery, required for ajax request 'https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2025.326.11214/dist/crowdin.js', 'https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2025.326.11214/dist/ranking-sorter.js', 'js/crowdin.js', # crowdin language selector