mirror of
https://github.com/LizardByte/libvirtualhid.git
synced 2026-11-13 15:32:41 +09:00
348 lines
13 KiB
JavaScript
348 lines
13 KiB
JavaScript
// projects section script
|
|
|
|
// get project container
|
|
let container
|
|
let org_name = "LizardByte"
|
|
let base_url = `https://app.${org_name.toLowerCase()}.dev`
|
|
let cache_repo = "dashboard"
|
|
const organizations_url = "https://raw.githubusercontent.com/LizardByte/.github/master/organizations.json"
|
|
let shared_web_url = "https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2026.1107.31840"
|
|
|
|
|
|
function onDocumentReady(callback) {
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", callback)
|
|
return
|
|
}
|
|
|
|
callback()
|
|
}
|
|
|
|
function fetchWithNoCache(url, options = {}) {
|
|
return fetch(url, {
|
|
...options,
|
|
cache: "no-store",
|
|
})
|
|
}
|
|
|
|
function fetchJson(url) {
|
|
return fetchWithNoCache(url)
|
|
.then(function (response) {
|
|
if (!response.ok) {
|
|
throw new Error(`Request failed for ${url}: ${response.status}`)
|
|
}
|
|
|
|
return response.json()
|
|
})
|
|
}
|
|
|
|
function shouldProcessRepo(repo) {
|
|
if (repo['private'] === true) return false;
|
|
if (repo['archived'] === true) return false;
|
|
if (repo['description'] === null) return false;
|
|
return !(repo['fork'] === true && !repo['topics'].includes("featured-fork"));
|
|
}
|
|
|
|
function projectUrl(repo) {
|
|
try {
|
|
const homepage = new URL(repo.homepage)
|
|
if (["https:", "http:"].includes(homepage.protocol)) return homepage.href
|
|
} catch {
|
|
// Repositories without a valid homepage link to GitHub.
|
|
}
|
|
return repo.html_url
|
|
}
|
|
|
|
function githubIdentity(value) {
|
|
try {
|
|
const url = new URL(value.replace(/^git@github\.com:/i, "https://github.com/"))
|
|
const parts = url.pathname.split("/").filter(Boolean)
|
|
if (url.hostname !== "github.com" || parts.length !== 2) return null
|
|
return parts.join("/").replace(/\.git$/i, "").toLowerCase()
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function createBannerElement(repo) {
|
|
let banner_div = document.createElement("div")
|
|
banner_div.className = "hover-zoom"
|
|
|
|
let banner_link = document.createElement("a")
|
|
banner_link.className = "crowdin-ignore"
|
|
banner_link.href = projectUrl(repo)
|
|
banner_link.target = "_blank"
|
|
banner_link.rel = "noopener noreferrer"
|
|
banner_div.append(banner_link)
|
|
|
|
let banner = document.createElement("img")
|
|
banner.className = "card-img-top rounded-0"
|
|
banner.src = `${base_url}/${cache_repo}/${repo.dashboard_paths.banner_small}`
|
|
banner.alt = ""
|
|
banner_link.append(banner)
|
|
|
|
return banner_div
|
|
}
|
|
|
|
function createCardBody(repo) {
|
|
let card_body = document.createElement("div")
|
|
card_body.className = "card-body p-4 rounded-0"
|
|
|
|
let card_title_link = document.createElement("a")
|
|
card_title_link.className = "text-decoration-none project-card-link crowdin-ignore"
|
|
card_title_link.href = projectUrl(repo)
|
|
card_title_link.target = "_blank"
|
|
card_title_link.rel = "noopener noreferrer"
|
|
card_body.appendChild(card_title_link)
|
|
|
|
let card_title_text = document.createElement("h5")
|
|
card_title_text.className = "card-title mb-3 fw-bolder crowdin-ignore"
|
|
card_title_text.textContent = repo['name']
|
|
|
|
if (repo['fork'] === true && repo['topics'].includes("featured-fork")) {
|
|
let fork_badge = document.createElement("span")
|
|
fork_badge.className = "badge bg-info ms-2"
|
|
fork_badge.textContent = "Fork"
|
|
card_title_text.appendChild(fork_badge)
|
|
}
|
|
|
|
card_title_link.appendChild(card_title_text)
|
|
|
|
const organization = document.createElement("p")
|
|
organization.className = "project-organization small text-muted crowdin-ignore"
|
|
organization.textContent = repo.owner.login
|
|
card_body.appendChild(organization)
|
|
|
|
let card_paragraph = document.createElement("p")
|
|
card_paragraph.className = "card-text mb-0"
|
|
card_paragraph.textContent = repo['description']
|
|
card_body.appendChild(card_paragraph)
|
|
|
|
return card_body
|
|
}
|
|
|
|
function loadCommitActivity(repo, card_footer) {
|
|
fetchJson(`${base_url}/${cache_repo}/github/commitActivity/${repo.dashboard_key}.json`)
|
|
.then(function (commitActivity) {
|
|
let activity_container = document.createElement("div")
|
|
activity_container.className = "commit-activity-graph mt-2 mb-2 mx-3"
|
|
activity_container.style.cssText = "display: flex; gap: 2px; height: 32px; align-items: flex-end;"
|
|
|
|
let maxCommits = Math.max(...commitActivity.map(week => week.total), 0)
|
|
|
|
for (let week of commitActivity) {
|
|
let bar = document.createElement("div")
|
|
let height = maxCommits > 0 ? (week.total / maxCommits) * 100 : 0
|
|
let intensity = 0.3 + (height / 100) * 0.7
|
|
|
|
bar.className = week.total === 0 ? 'commit-bar-empty crowdin-ignore' : 'commit-bar-active crowdin-ignore'
|
|
bar.style.cssText = `
|
|
flex: 1;
|
|
min-width: 2px;
|
|
height: ${Math.max(height, 10)}%;
|
|
border-radius: 1px;
|
|
transition: all 0.2s ease;
|
|
--commit-bar-opacity: ${intensity};
|
|
`
|
|
bar.title = `${week.total} commits this week`
|
|
activity_container.appendChild(bar)
|
|
}
|
|
|
|
card_footer.insertBefore(activity_container, card_footer.firstChild)
|
|
})
|
|
.catch(function () {
|
|
console.log(`No commit activity data available for ${repo['name']}`)
|
|
})
|
|
}
|
|
|
|
function createRepoDataRow(repo, card_footer) {
|
|
let repo_data_row = document.createElement("div")
|
|
repo_data_row.className = "d-flex align-items-center"
|
|
card_footer.appendChild(repo_data_row)
|
|
|
|
let github_link = document.createElement("a")
|
|
github_link.className = "nav-link project-nav-link ms-3"
|
|
github_link.href = repo['html_url']
|
|
github_link.target = "_blank"
|
|
repo_data_row.appendChild(github_link)
|
|
|
|
let github_link_image = document.createElement("i")
|
|
github_link_image.className = "fa-fw fa-brands fa-github"
|
|
github_link.prepend(github_link_image)
|
|
|
|
let star_link = document.createElement("a")
|
|
star_link.className = "nav-link nav-link-sm project-nav-link ms-3 crowdin-ignore"
|
|
star_link.href = `https://star-history.com/#${repo['full_name']}`
|
|
star_link.target = "_blank"
|
|
star_link.textContent = globalThis.formatNumber(repo['stargazers_count'])
|
|
repo_data_row.appendChild(star_link)
|
|
|
|
let star_link_image = document.createElement("i")
|
|
star_link_image.className = "fa-fw fa-solid fa-star"
|
|
star_link.prepend(star_link_image)
|
|
|
|
let fork_link = document.createElement("a")
|
|
fork_link.className = "nav-link nav-link-sm project-nav-link ms-3 crowdin-ignore"
|
|
fork_link.href = `https://github.com/${repo['full_name']}/network/members`
|
|
fork_link.target = "_blank"
|
|
fork_link.textContent = globalThis.formatNumber(repo['forks'])
|
|
repo_data_row.appendChild(fork_link)
|
|
|
|
let fork_link_image = document.createElement("i")
|
|
fork_link_image.className = "fa-fw fa-solid fa-code-fork"
|
|
fork_link.prepend(fork_link_image)
|
|
|
|
return repo_data_row
|
|
}
|
|
|
|
function addDocsLink(repo, readthedocs, repo_data_row) {
|
|
for (let docs in readthedocs) {
|
|
const docs_repo = githubIdentity(readthedocs[docs]['repository']['url']);
|
|
if (!repo.dashboard_aliases.some(alias => alias.toLowerCase() === docs_repo)) continue;
|
|
|
|
let docs_url = readthedocs[docs]['urls']['documentation']
|
|
try {
|
|
let parsedUrl = new URL(docs_url);
|
|
if (parsedUrl.host !== "docs.lizardbyte.dev") continue;
|
|
} catch (e) {
|
|
console.error("Invalid URL:", docs_url, e);
|
|
continue;
|
|
}
|
|
|
|
let docs_link = document.createElement("a")
|
|
docs_link.className = "nav-link ms-3"
|
|
docs_link.href = docs_url
|
|
docs_link.target = "_blank"
|
|
repo_data_row.appendChild(docs_link)
|
|
|
|
let docs_link_image = document.createElement("img")
|
|
docs_link_image.className = "readthedocs-icon"
|
|
docs_link_image.src = "https://cdn.jsdelivr.net/npm/simple-icons@16/icons/readthedocs.svg"
|
|
docs_link_image.alt = "ReadTheDocs"
|
|
docs_link_image.title = "ReadTheDocs"
|
|
docs_link_image.style.width = "24px"
|
|
docs_link_image.style.height = "24px"
|
|
docs_link.prepend(docs_link_image)
|
|
}
|
|
}
|
|
|
|
function loadLanguageIcons(repo, card_footer) {
|
|
fetchJson(`${base_url}/${cache_repo}/github/languages/${repo.dashboard_key}.json`)
|
|
.then(function (languages) {
|
|
let language_data_row = document.createElement("div")
|
|
language_data_row.className = "card-group p-3 align-items-center"
|
|
card_footer.appendChild(language_data_row)
|
|
|
|
for (let language in languages) {
|
|
let language_file = encodeURIComponent(`${language}.svg`)
|
|
let language_icon = document.createElement("img")
|
|
language_icon.className = "language-logo crowdin-ignore"
|
|
language_icon.src = `${shared_web_url}/dist/language-icons/${language_file}`
|
|
language_icon.alt = language
|
|
language_icon.title = language
|
|
language_data_row.append(language_icon)
|
|
}
|
|
})
|
|
.catch(function () {})
|
|
}
|
|
|
|
function buildRepoCard(repo, readthedocs) {
|
|
let column = document.createElement("div")
|
|
column.className = "col-lg-4 mb-5"
|
|
container.appendChild(column)
|
|
|
|
let card = document.createElement("div")
|
|
card.className = "card h-100 shadow border-0 rounded-0"
|
|
column.appendChild(card)
|
|
|
|
card.append(createBannerElement(repo))
|
|
|
|
card.appendChild(createCardBody(repo))
|
|
|
|
let card_footer = document.createElement("div")
|
|
card_footer.className = "card-footer p-2 pt-0 border-0 rounded-0"
|
|
card.appendChild(card_footer)
|
|
|
|
loadCommitActivity(repo, card_footer)
|
|
|
|
let repo_data_row = createRepoDataRow(repo, card_footer)
|
|
addDocsLink(repo, readthedocs, repo_data_row)
|
|
loadLanguageIcons(repo, card_footer)
|
|
return column
|
|
}
|
|
|
|
function updateProjectFilters(cards, organizations) {
|
|
const organization = document.getElementById("project-organization").value
|
|
const terms = document.getElementById("project-search").value.toLowerCase().trim().split(/\s+/).filter(Boolean)
|
|
let count = 0
|
|
for (const { repo, column } of cards) {
|
|
const text = `${repo.full_name} ${repo.description}`.toLowerCase()
|
|
const matches = (!organization || repo.owner.login.toLowerCase() === organization.toLowerCase())
|
|
&& terms.every(term => text.includes(term))
|
|
column.hidden = !matches
|
|
if (matches) count += 1
|
|
}
|
|
const noun = count === 1 ? "project" : "projects"
|
|
document.getElementById("project-status").textContent = count
|
|
? `${count} ${noun}` : "No projects match your search."
|
|
|
|
const scope = organization ? [organization] : organizations
|
|
const query = [...scope.map(name => `org:${name}`), "archived:true"].join(" ")
|
|
const archive = document.getElementById("archived-projects-link")
|
|
archive.href = `https://github.com/search?${new URLSearchParams({ q: query, type: "repositories" })}`
|
|
archive.classList.remove("disabled")
|
|
archive.removeAttribute("aria-disabled")
|
|
}
|
|
|
|
function createProjectBrowser(repos, readthedocs, organizations) {
|
|
if (!Array.isArray(organizations) || !organizations.length
|
|
|| organizations.some(name => typeof name !== "string" || !/^[a-z\d][a-z\d-]*$/i.test(name))) {
|
|
throw new Error("Invalid organization registry")
|
|
}
|
|
const organization = document.getElementById("project-organization")
|
|
organizations.sort((left, right) => left.localeCompare(right))
|
|
for (const name of organizations) {
|
|
organization.add(new Option(name, name))
|
|
}
|
|
const registered = new Set(organizations.map(name => name.toLowerCase()))
|
|
const sorted = repos.filter(repo => registered.has(repo.owner.login.toLowerCase()) && shouldProcessRepo(repo))
|
|
.sort(globalThis.rankingSorter("stargazers_count", "full_name"))
|
|
// Build each card once so filtering does not fetch its assets again.
|
|
const cards = sorted.map(repo => ({ repo, column: buildRepoCard(repo, readthedocs) }))
|
|
const update = () => updateProjectFilters(cards, organizations)
|
|
const search = document.getElementById("project-search")
|
|
organization.disabled = false
|
|
search.disabled = false
|
|
organization.addEventListener("change", update)
|
|
search.addEventListener("input", update)
|
|
update()
|
|
}
|
|
|
|
|
|
// create project cards
|
|
onDocumentReady(function () {
|
|
container = document.getElementById("project-container")
|
|
if (!container) return
|
|
|
|
let readthedocsRequest = fetchJson(`${base_url}/${cache_repo}/readthedocs/projects.json`)
|
|
.then(function (data) {
|
|
return Object.values(data)
|
|
})
|
|
.catch(function (error) {
|
|
console.log("No ReadTheDocs project data available", error)
|
|
return []
|
|
})
|
|
|
|
let reposRequest = fetchJson(`${base_url}/${cache_repo}/github/repos.json`)
|
|
let organizationsRequest = fetchJson(organizations_url)
|
|
|
|
Promise.all([readthedocsRequest, reposRequest, organizationsRequest])
|
|
.then(function ([readthedocs, result, organizations]) {
|
|
createProjectBrowser(result, readthedocs, organizations)
|
|
})
|
|
.catch(function (error) {
|
|
document.getElementById("project-status").textContent = "Projects could not be loaded. Please try again later."
|
|
console.error("Error loading project data:", error)
|
|
})
|
|
})
|