Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e609143
Add project metadata, templates, and MIT license
LoveDoLove Aug 28, 2025
c31d6ac
Add open source projects showcase page
LoveDoLove Aug 28, 2025
5765b3b
Update projects.json with new project entries
LoveDoLove Aug 28, 2025
5174418
Bump version to 1.0.0 in package.json
LoveDoLove Aug 28, 2025
195a18c
Merge branch 'main' into lovedolove
LoveDoLove Aug 28, 2025
c921acc
Add icon and favicon image assets
LoveDoLove Aug 28, 2025
1b13170
Add README and update favicon assets
LoveDoLove Aug 28, 2025
8077bc3
Update logo image source in README
LoveDoLove Aug 28, 2025
479c511
Bump version to 1.0.1
LoveDoLove Aug 28, 2025
dd3abb9
Merge branch 'main' into lovedolove
LoveDoLove Aug 28, 2025
8f888dc
Automate repo metadata fetching and update structure
LoveDoLove Sep 9, 2025
07e2820
Bump version to 1.0.2-1
LoveDoLove Sep 9, 2025
285179e
Merge branch 'main' into lovedolove
LoveDoLove Sep 9, 2025
f073380
Add node-fetch as a dependency
LoveDoLove Sep 9, 2025
7f2939f
Update fetch import for compatibility with global fetch
LoveDoLove Sep 9, 2025
3326718
Add write permissions to workflow
LoveDoLove Sep 9, 2025
d48254e
Update public/data/projects.json with latest repo metadata
LoveDoLove Sep 9, 2025
f9b4b27
Remove projects.json from public data
LoveDoLove Sep 9, 2025
f0dd579
Bump version to 1.0.2 in package files
LoveDoLove Sep 9, 2025
6c0bcae
Merge branch 'main' into lovedolove
LoveDoLove Sep 9, 2025
0ea14a4
Add projects.json data file
LoveDoLove Sep 9, 2025
c3d506c
Bump version to 1.0.3
LoveDoLove Sep 9, 2025
0fae02f
Add workflows to clean up workflow runs
LoveDoLove Sep 9, 2025
ebd73e6
Merge branch 'main' into lovedolove
LoveDoLove Sep 9, 2025
df18b87
Refactor project showcase for modularity and SEO
LoveDoLove Sep 9, 2025
40ab280
Refactor project card rendering for metadata arrays
LoveDoLove Sep 9, 2025
bbf556d
Refactor project topics UI and add styles
LoveDoLove Sep 9, 2025
918641d
Add minified JS for project showcase
LoveDoLove Sep 9, 2025
45d27e7
Update index.html formatting and script reference
LoveDoLove Sep 9, 2025
d144051
Update LoveDoLove project list
LoveDoLove Sep 9, 2025
f5ad53d
Bump version to 1.0.4
LoveDoLove Sep 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion data/projects.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
{
"username": "LoveDoLove",
"projects": [
"open-source-projects",
"EasyKit",
"cloudflare-smart-tools",
"cloudflare-smart-cache",
"alist-freebsd",
"memos-freebsd",
"panindex-freebsd",
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-source-projects",
"version": "1.0.3",
"version": "1.0.4",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
Expand Down
322 changes: 54 additions & 268 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -1,270 +1,56 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<title>Open Source Projects Showcase</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
.project-container {
display: grid;
gap: 20px;
margin-top: 30px;
}
.project-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
background: #f9f9f9;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.project-title {
margin: 0 0 10px 0;
color: #333;
font-size: 1.5em;
}
.project-description {
color: #666;
margin-bottom: 15px;
}
.project-links {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.project-link {
padding: 8px 16px;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
transition: background-color 0.3s;
}
.github-link {
background: #24292e;
color: white;
}
.github-link:hover {
background: #444;
}
.redirect-link {
background: #007bff;
color: white;
}
.redirect-link:hover {
background: #0056b3;
}
.loading {
text-align: center;
color: #666;
font-style: italic;
}
.error {
color: #d32f2f;
background: #ffebee;
padding: 15px;
border-radius: 4px;
border-left: 4px solid #d32f2f;
}
</style>
</head>
<body>
<h1 id="heading">Open Source Projects Showcase</h1>
<p>Discover and explore our collection of open source projects.</p>

<!-- Container where projects will be dynamically rendered -->
<div id="projects-container" class="project-container">
<div class="loading">Loading projects...</div>
</div>

<script>
/**
* Main function to initialize the project showcase
* Fetches project data and renders the project list
*/
async function initializeProjectShowcase() {
try {
// Fetch project data from the JSON file
const projects = await fetchProjects();

// Render the projects to the DOM
renderProjects(projects);
} catch (error) {
// Display error message if fetching or rendering fails
displayError('Failed to load projects. Please try again later.');
console.error('Error initializing project showcase:', error);
}
}

/**
* Fetches project data from the /data/projects.json endpoint
* @returns {Promise<Array>} Array of project objects
*/
/**
* Updated fetchProjects for new nested provider/account structure
* @returns {Promise<Array>} Array of flattened project objects
*/
async function fetchProjects() {
const response = await fetch('/data/projects.json');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const accountsList = await response.json();
const projects = [];
accountsList.forEach(accountObj => {
['github', 'gitlab', 'gitee', 'bitbucket'].forEach(provider => {
const providerAccounts = accountObj[provider];
if (Array.isArray(providerAccounts)) {
providerAccounts.forEach(acc => {
if (acc.username && Array.isArray(acc.projects)) {
acc.projects.forEach(repo => {
projects.push({
provider,
username: acc.username,
repo
});
});
}
});
}
});
});
return projects;
}
async function fetchProjects() {
const response = await fetch('/data/projects.json');

// Check if the response is successful
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

// Parse and return the JSON data
const projects = await response.json();

// Validate that we received an array
if (!Array.isArray(projects)) {
throw new Error('Invalid data format: expected an array of projects');
}

return projects;
}

/**
* Renders the list of projects to the DOM
* @param {Array} projects - Array of project objects to render
*/
function renderProjects(projects) {
const container = document.getElementById('projects-container');

// Clear the loading message
container.innerHTML = '';

// Check if there are any projects to display
if (projects.length === 0) {
container.innerHTML = '<p class="loading">No projects found.</p>';
return;
}

// Create and append project cards for each project
projects.forEach(project => {
const projectCard = createProjectCard(project);
container.appendChild(projectCard);
});
}

/**
* Creates a DOM element for a single project card
* @param {Object} project - Project object with name, description, github_url, and hostname
* @returns {HTMLElement} DOM element representing the project card
*/
function createProjectCard(project) {
// Create the main project card container
const card = document.createElement('div');
card.className = 'project-card';

// Create and append the project title
const title = document.createElement('h2');
title.className = 'project-title';
title.textContent = project.name || 'Unnamed Project';
card.appendChild(title);

// Create and append the project description
const description = document.createElement('p');
description.className = 'project-description';
description.textContent = project.description || 'No description available.';
card.appendChild(description);

// Create the links container
const linksContainer = document.createElement('div');
linksContainer.className = 'project-links';

// Create GitHub link if URL is provided
if (project.github_url) {
const githubLink = createGitHubLink(project.github_url);
linksContainer.appendChild(githubLink);
}

// Create redirect link if hostname is provided
if (project.hostname) {
const redirectLink = createRedirectLink(project.hostname);
linksContainer.appendChild(redirectLink);
}

card.appendChild(linksContainer);

return card;
}

/**
* Creates a GitHub link element
* @param {string} githubUrl - The GitHub URL for the project
* @returns {HTMLElement} Anchor element linking to GitHub
*/
function createGitHubLink(githubUrl) {
const link = document.createElement('a');
link.href = githubUrl;
link.className = 'project-link github-link';
link.textContent = 'View on GitHub';
link.target = '_blank'; // Open in new tab
link.rel = 'noopener noreferrer'; // Security best practice
return link;
}

/**
* Creates an active redirect link that points to the project's hostname
* @param {string} hostname - The hostname for the project (e.g., 'example.com')
* @returns {HTMLElement} Anchor element linking to the project's website
*/
function createRedirectLink(hostname) {
const link = document.createElement('a');
// Construct the full HTTPS URL from the hostname
link.href = `https://${hostname}`;
link.className = 'project-link redirect-link';
link.textContent = `Visit ${hostname}`;
link.target = '_blank'; // Open in new tab for better user experience
link.rel = 'noopener noreferrer'; // Security best practice to prevent tab hijacking
link.title = `Visit the live project at ${hostname}`;
return link;
}

/**
* Displays an error message to the user
* @param {string} message - Error message to display
*/
function displayError(message) {
const container = document.getElementById('projects-container');
container.innerHTML = `<div class="error">${message}</div>`;
}

// Initialize the project showcase when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initializeProjectShowcase);
</script>
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Discover and explore a curated collection of open source projects from GitHub, GitLab, Gitee, and Bitbucket."
/>
<meta property="og:title" content="Open Source Projects Showcase" />
<meta
property="og:description"
content="Discover and explore a curated collection of open source projects from multiple platforms."
/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://yourdomain.com/" />
<meta
property="og:image"
content="https://yourdomain.com/images/icon.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Open Source Projects Showcase" />
<meta
name="twitter:description"
content="Discover and explore a curated collection of open source projects from multiple platforms."
/>
<meta
name="twitter:image"
content="https://yourdomain.com/images/icon.png"
/>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<title>Open Source Projects Showcase</title>
<link rel="preload" href="style.min.css" as="style" />
<link rel="stylesheet" href="style.min.css" />
</head>
<body>
<main>
<header>
<h1 id="heading">Open Source Projects Showcase</h1>
<p>Discover and explore our collection of open source projects.</p>
</header>
<!-- Container where projects will be dynamically rendered -->
<section
id="projects-container"
class="project-container"
aria-live="polite"
aria-label="Project list"
>
<div class="loading" role="status" aria-live="polite">
Loading projects...
</div>
</section>
</main>
<script src="index.min.js" defer></script>
</body>
</html>
Loading