Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ Welcome to your new catalog repo! The primary way to personalize this catalog is
* `COLORS.tag`: Tag background color

* **API & Behavior Settings:**
* `PLATFORM`: Coding platform used (default: 'github', Codeberg and GitLab support in development)
* `API_BASE_URL`: Hugging Face API base URL (default: `"https://huggingface.co/api/"`)
* `REFRESH_INTERVAL_DAYS`: Number of days to consider an item "new" (default: `30`)
* `ADDITIONAL_REPOS`: Array of forked or non-org GitHub repositories to include, formatted `<owner>/<repo-name>` (non-forks are included by default). Use `[]` if there are none you wish to include
Expand Down
14 changes: 6 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,18 @@
</head>

<body class="p-8 bg-gray-100 dark:bg-slate-900 transition-colors duration-200">
<!-- GitHub Ribbon -->
<a id="github-ribbon" href="#" target="_blank"
<!-- Code Repo Ribbon -->
<a id="repo-ribbon" href="#" target="_blank"
class="fixed top-6 right-6 hidden md:flex items-center gap-3 text-white py-2 px-4 rounded-lg shadow-md transition-all z-50 no-underline group font-sans border border-white/10">
<!-- Github Logo -->
<!-- Code Platform Logo -->
<div class="flex-shrink-0">
<svg class="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"></path>
<path id="repo-ribbon-icon" fill-rule="evenodd" clip-rule="evenodd"></path>
</svg>
</div>
<div class="flex flex-col justify-center">
<!-- Github Text -->
<span class="font-normal text-base leading-tight mb-0.5">GitHub</span>
<!-- Platform Display Name -->
<span id="platform-display-name" class="font-normal text-base leading-tight mb-0.5"></span>
Comment thread
egrace479 marked this conversation as resolved.
<!-- Stats Container -->
<div class="flex items-center gap-3 text-xs font-light text-indigo-100">
<!-- Version -->
Expand Down
55 changes: 35 additions & 20 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import jsYaml from 'js-yaml';
import { normalizeTag } from './src/normalizeTag.js';
import { getPlatformApiUrls } from './src/defineApiUrls.js';
import { getPlatformDisplay } from './src/defineRibbonVals.js';
import { filterItems, sortItems } from './src/filterAndSort.js';
import { filterNewAdditionalEntries } from './src/filterNewAdditionalEntries.js';

Expand All @@ -22,7 +24,8 @@ const configPromise = fetch('config.yaml')

// Module-scope lets — assigned after config loads, used by all functions below
let CONFIG;
let ORGANIZATION_NAME, CATALOG_REPO_NAME, API_BASE_URL, REFRESH_INTERVAL_DAYS, ADDITIONAL_REPOS, ADDITIONAL_HF_REPOS;
let ORGANIZATION_NAME, CATALOG_REPO_NAME, PLATFORM, API_BASE_URL, REFRESH_INTERVAL_DAYS, ADDITIONAL_REPOS, ADDITIONAL_HF_REPOS;
let ORG_API_URL, REPO_API_URL;

// Build a reverse lookup from TAG_GROUPS (defined in tag-groups.js): raw tag → [canonical tags]
// A raw tag may appear in multiple groups, so the value is an array.
Expand Down Expand Up @@ -162,7 +165,7 @@ const getCurrentState = () => {
//

/**
* Fetches items (code, datasets, models, or spaces) for a given organization from the GitHub or Hugging Face API.
* Fetches items (code, datasets, models, or spaces) for a given organization from the specified code platform or Hugging Face API.
* @async
* @param {string} repoType - The type of repository to fetch ("code", "datasets", "models", or "spaces").
* @returns {Promise<Array>} An array of item objects.
Expand All @@ -178,18 +181,19 @@ const fetchHubItems = async (repoType) => {
try {
let items = [];

// github api requests for code
// Platform-specific api requests for code
if (repoType === "code") {
if (fetchedData.code) return allItems.code // reuse if already fetched

// Paginate through all public repos (GitHub API returns max 100 per page)
// Paginate through all public repos (Platform determines API max returns per page)
let allRepos = [];
let nextUrl = `https://api.github.com/orgs/${ORGANIZATION_NAME}/repos?type=public&per_page=100`;
let nextUrl = `${ORG_API_URL}`;
while (nextUrl) {
const ghResponse = await fetch(nextUrl);

if (!ghResponse.ok) {
throw new Error(`GitHub error: ${ghResponse.status}`);
const platformDisplay = getPlatformDisplay(PLATFORM);
throw new Error(`${platformDisplay.displayName || PLATFORM} error: ${ghResponse.status}`);
}

const page = await ghResponse.json();
Expand All @@ -209,7 +213,7 @@ const fetchHubItems = async (repoType) => {

const fetchedExternalData = await Promise.all(
toFetch.map(ownerRepo =>
fetch(`https://api.github.com/repos/${ownerRepo}`)
fetch(`${REPO_API_URL}${ownerRepo}`)
.then(r => {
if (!r.ok) {
console.warn(`Failed to fetch additional repo "${ownerRepo}": HTTP ${r.status}`);
Expand Down Expand Up @@ -382,17 +386,19 @@ const fetchCatalogStats = async () => {
};

try {
//TODO: Update stars and forks to support other platforms (GitLab, Codeberg) once implemented
// 1. Get Stars & Forks
const repo = await fetch(`https://api.github.com/repos/${ORGANIZATION_NAME}/${CATALOG_REPO_NAME}`).then(r => r.ok ? r.json() : {});
const repo = await fetch(`${REPO_API_URL}${ORGANIZATION_NAME}/${CATALOG_REPO_NAME}`).then(r => r.ok ? r.json() : {});
if (repo.stargazers_count !== undefined) update('gh-stars', 'gh-star-container', repo.stargazers_count);
if (repo.forks_count !== undefined) update('gh-forks', 'gh-fork-container', repo.forks_count);

// 2. Get Version (Tag)
const release = await fetch(`https://api.github.com/repos/${ORGANIZATION_NAME}/${CATALOG_REPO_NAME}/releases/latest`).then(r => r.ok ? r.json() : {});
// TODO: Import from package.json
const release = await fetch(`${REPO_API_URL}${ORGANIZATION_NAME}/${CATALOG_REPO_NAME}/releases/latest`).then(r => r.ok ? r.json() : {});
if (release.tag_name) update('gh-tag', 'gh-version-container', release.tag_name);
Comment thread
egrace479 marked this conversation as resolved.

} catch (e) {
console.warn("Could not fetch GitHub stats", e);
console.warn("Could not fetch Code Repo stats", e);
}
};

Expand Down Expand Up @@ -469,7 +475,7 @@ const renderHubItemCard = (item, repoType) => {
break;
}

// stars for GitHub repos
// stars for code repos
const badgeHtml = (() => {
if (item.isNew) {
return `<span class="new-badge inline-block text-xs font-bold text-white rounded-full px-2 py-1">
Expand Down Expand Up @@ -628,7 +634,7 @@ const populateTagFilter = (repoType) => {

/**
* Initializes UI elements from configuration values.
* This sets up the header, logo, GitHub ribbon, and dynamic styles.
* This sets up the header, logo, repo ribbon, and dynamic styles.
*/
const initializeUIFromConfig = () => {
// Set header logo
Expand All @@ -654,16 +660,21 @@ const initializeUIFromConfig = () => {
headerDesc.textContent = CONFIG.CATALOG_DESCRIPTION;
}

// Set GitHub ribbon link and colors
const githubRibbon = document.getElementById('github-ribbon');
if (githubRibbon) {
githubRibbon.href = `https://github.com/${ORGANIZATION_NAME}/${CATALOG_REPO_NAME}`;
githubRibbon.style.backgroundColor = CONFIG.COLORS.secondary;
githubRibbon.style.setProperty('--hover-color', CONFIG.COLORS.primary);
githubRibbon.addEventListener('mouseenter', function () {
// Set Code Repo ribbon link, SVG path, display name, and colors
const repoRibbon = document.getElementById('repo-ribbon');
const platformDisplay = getPlatformDisplay(PLATFORM);
if (repoRibbon && platformDisplay) {
repoRibbon.href = `${platformDisplay.ribbonUrl}${ORGANIZATION_NAME}/${CATALOG_REPO_NAME}`;
const pathElement = document.getElementById('repo-ribbon-icon');
pathElement.setAttribute('d', platformDisplay.path);
const platformDisplayName = document.getElementById('platform-display-name');
platformDisplayName.textContent = platformDisplay.displayName || PLATFORM;
repoRibbon.style.backgroundColor = CONFIG.COLORS.secondary;
repoRibbon.style.setProperty('--hover-color', CONFIG.COLORS.primary);
repoRibbon.addEventListener('mouseenter', function () {
this.style.backgroundColor = CONFIG.COLORS.primary;
});
githubRibbon.addEventListener('mouseleave', function () {
repoRibbon.addEventListener('mouseleave', function () {
this.style.backgroundColor = CONFIG.COLORS.secondary;
});
}
Expand Down Expand Up @@ -696,6 +707,7 @@ document.addEventListener('DOMContentLoaded', async () => {
CATALOG_TITLE: 'Catalog', CATALOG_DESCRIPTION: '',
LOGO_URL: '', FAVICON_URL: '',
COLORS: { primary: '#92991c', secondary: '#5d8095', accent: '#0097b2', accentDark: '#4fd1eb', tag: '#9bcb5e' },
PLATFORM: 'github',
API_BASE_URL: 'https://huggingface.co/api/', REFRESH_INTERVAL_DAYS: 30,
ADDITIONAL_REPOS: [], ADDITIONAL_HF_REPOS: [], FONT_FAMILY: 'Inter'
};
Expand All @@ -704,6 +716,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// Assign module-scope variables used by all functions
ORGANIZATION_NAME = CONFIG.ORGANIZATION_NAME;
CATALOG_REPO_NAME = CONFIG.CATALOG_REPO_NAME;
PLATFORM = CONFIG.PLATFORM;
ORG_API_URL = getPlatformApiUrls(PLATFORM, ORGANIZATION_NAME).org;
REPO_API_URL = getPlatformApiUrls(PLATFORM, ORGANIZATION_NAME).repo;
API_BASE_URL = CONFIG.API_BASE_URL;
REFRESH_INTERVAL_DAYS = CONFIG.REFRESH_INTERVAL_DAYS;
ADDITIONAL_REPOS = CONFIG.ADDITIONAL_REPOS;
Expand Down
4 changes: 4 additions & 0 deletions public/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ COLORS:
tag: "#9bcb5e" # Tag background color (Light Green)

# API & Behavior Settings
# Codebase platform for API calls and link generation. Supported values: "github", pending: "codeberg" or "gitlab".
PLATFORM: github
# Dataset, model, and space (demo) default: Hugging Face, other platforms would require a custom module
API_BASE_URL: "https://huggingface.co/api/"
# Define "new" repository criteria
REFRESH_INTERVAL_DAYS: 30

# Array of "owner/repo" strings to include in addition to non-forked org repos.
Expand Down
18 changes: 11 additions & 7 deletions scripts/export-tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import path from 'path';
import { fileURLToPath } from 'url';
import jsYaml from 'js-yaml';
import { validateConfig } from '../src/validateConfig.js';
import { getPlatformApiUrls } from '..src/defineApiUrls.js';
import { filterNewAdditionalEntries } from '../src/filterNewAdditionalEntries.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
Expand All @@ -33,12 +34,13 @@ if (errors.length) {
}

const CONFIG = rawConfig;
const { ORGANIZATION_NAME, API_BASE_URL, ADDITIONAL_REPOS } = CONFIG;
const { ORGANIZATION_NAME, PLATFORM, API_BASE_URL, ADDITIONAL_REPOS } = CONFIG;
const ADDITIONAL_HF_REPOS = CONFIG.ADDITIONAL_HF_REPOS;

// ---------------------------------------------------------------------------
// Fetch helpers
// ---------------------------------------------------------------------------
// Update this section as needed for non-GitHub code platforms (e.g., Codeberg or GitLab)
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;

const get = async (url) => {
Expand All @@ -56,11 +58,13 @@ const get = async (url) => {
// Tag collection
// ---------------------------------------------------------------------------
const allTags = new Set();
const { org: ORG_API_URL, repo: REPO_API_URL } = getPlatformApiUrls(PLATFORM, ORGANIZATION_NAME);

const collectGitHubTags = async () => {
console.log('Fetching GitHub repos...');
const collectCodePlatformTags = async () => {
const platformDisplay = getPlatformDisplay(PLATFORM);
console.log(`Fetching ${platformDisplay.displayName || PLATFORM} repos...`);
let allRepos = [];
let nextUrl = `https://api.github.com/orgs/${ORGANIZATION_NAME}/repos?type=public&per_page=100`;
let nextUrl = `${ORG_API_URL}`;

while (nextUrl) {
const { json: page, headers } = await get(nextUrl);
Expand All @@ -73,7 +77,7 @@ const collectGitHubTags = async () => {
// Additional repos
const additionalData = await Promise.all(
ADDITIONAL_REPOS.map(ownerRepo =>
get(`https://api.github.com/repos/${ownerRepo}`)
get(`${REPO_API_URL}${ownerRepo}`)
.then(({ json }) => json)
.catch(() => null)
)
Expand All @@ -87,7 +91,7 @@ const collectGitHubTags = async () => {
(repo.topics || []).forEach(t => allTags.add(t.toLowerCase()));
});

console.log(` GitHub: processed ${orgNonForks.length} org repos + ${additionalRepos.length} additional repos`);
console.log(` ${platformDisplay.displayName || PLATFORM}: processed ${orgNonForks.length} org repos + ${additionalRepos.length} additional repos`);
};

const collectHFTags = async (repoType) => {
Expand Down Expand Up @@ -133,7 +137,7 @@ const collectHFTags = async (repoType) => {
// ---------------------------------------------------------------------------
(async () => {
try {
await collectGitHubTags();
await collectCodePlatformTags();
await collectHFTags('datasets');
await collectHFTags('models');
await collectHFTags('spaces');
Expand Down
6 changes: 4 additions & 2 deletions scripts/fetch-releases.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import jsYaml from 'js-yaml';
import { validateConfig } from '../src/validateConfig.js';
import { getPlatformApiUrls } from '../src/defineApiUrls.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Expand All @@ -27,8 +28,9 @@ const headers = TOKEN
const TWO_WEEKS_MS = 14 * 24 * 60 * 60 * 1000;

// Step 1: Fetch all public org repos (paginated, same logic as main.js)
const { org: ORG_API_URL, repo: REPO_API_URL } = getPlatformApiUrls(CONFIG.PLATFORM, CONFIG.ORGANIZATION_NAME);
let allOrgRepos = [];
let nextUrl = `https://api.github.com/orgs/${CONFIG.ORGANIZATION_NAME}/repos?type=public&per_page=100`;
let nextUrl = `${ORG_API_URL}`;
while (nextUrl) {
const res = await fetch(nextUrl, { headers });
if (!res.ok) {
Expand All @@ -55,7 +57,7 @@ const repoIds = [
const releases = {};
for (const id of repoIds) {
try {
const res = await fetch(`https://api.github.com/repos/${id}/releases/latest`, { headers });
const res = await fetch(`${REPO_API_URL}${id}/releases/latest`, { headers });
if (!res.ok) { releases[id] = null; continue; }
const data = await res.json();
releases[id] = {
Expand Down
34 changes: 34 additions & 0 deletions src/defineApiUrls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Defines API URLs based on the selected platform (GitHub, note: GitLab and Codeberg support under development).
* This allows the rest of the codebase to use these constants when making API calls,
* abstracting away platform-specific URL structures.
*
* Usage: import { getPlatformApiUrls } from './defineApiUrls.js';
*
* Input: platform and organizationName (e.g., 'github' and 'imageomics'), defined from config.yaml and passed to this function.
* Output: platformApiUrls[platform] = { org: ORG_API_URL, repo: REPO_API_URL }
*/

/**
* Utility function to get the platform-specific API URLs for organization repos and individual repo details.
* @param {string} platform - 'github', pending: 'gitlab', or 'codeberg'
* @param {string} organizationName - The name of the organization (used in URL construction)
* @returns {object} An object containing ORG_API_URL and REPO_API_URL
*/
export function getPlatformApiUrls(platform, organizationName) {
const platformApiUrls = {
github: {
org: `https://api.github.com/orgs/${organizationName}/repos?type=public&per_page=100`,
repo: "https://api.github.com/repos/"
},
// gitlab: {
// org: `https://gitlab.com/api/v4/groups/${organizationName}/projects?per_page=100`,
// repo: "https://gitlab.com/api/v4/projects/"
// },
// codeberg: {
// org: `https://codeberg.org/api/v1/orgs/${organizationName}/repos?limit=50`,
// repo: "https://codeberg.org/api/v1/repos/"
// }
};
return platformApiUrls[platform.toLowerCase()];
}
43 changes: 43 additions & 0 deletions src/defineRibbonVals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* A collection of platform-specific display information (e.g., SVG path data)
* for code developer platforms including GitHub, pending: GitLab and Codeberg.
* Used for linking to repo and rendering icon and platform name in the ribbon component of the UI.
* All paths are optimized for a 0 0 24 24 viewBox.
*
* Usage: import { getPlatformDisplay } from './defineRibbonVals.js';
*
* Input: platform (e.g., 'github'), defined from config.yaml and passed to this function.
* Output: platformDisplays[platform] = { path: SVG_PATH_DATA, viewBox: VIEWBOX_DATA,
* displayName: DISPLAY_NAME, ribbonUrl: RIBBON_URL }
*/

/**
* Utility function to get the full platform display information
* @param {string} platform - 'github', 'gitlab', or 'codeberg'
* @returns {object|null}
*/
export function getPlatformDisplay(platform) {
const platformDisplays = {
github: {
path: "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z",
viewBox: "0 0 24 24",
displayName: "GitHub",
ribbonUrl: "https://github.com/"
},
// gitlab: {
// path: "m23.71 11.83-2.13-6.52a.89.89 0 0 0-1.7 0l-2.13 6.52H6.25L4.12 5.31a.89.89 0 0 0-1.7 0L.29 11.83a1 1 0 0 0 .37 1.13l11.34 8.24 11.34-8.24a1 1 0 0 0 .37-1.13Z",
// viewBox: "0 0 24 24",
// color: "#FC6D26",
// displayName: "GitLab",
// ribbonUrl: "https://gitlab.com/"
//},
// codeberg: {
// path: "M12.004 2.378a.636.636 0 0 0-.547.31L1.258 19.874a.636.636 0 0 0 .547.954h1.94c.143 0 .278-.074.354-.197L12 6.544l7.89 14.087a.406.406 0 0 0 .354.197h2.012a.636.636 0 0 0 .546-.954L12.551 2.688a.636.636 0 0 0-.547-.31Z",
// viewBox: "0 0 24 24",
// color: "#2185d0",
// displayName: "Codeberg",
// ribbonUrl: "https://codeberg.org/"
//}
};
return platformDisplays[platform.toLowerCase()];
}
Loading