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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ GITHUB_REPO=
# A GitHub personal access token with the required permissions for your app.
# Never share or commit your real token!
GITHUB_ACCESS_TOKEN=

# Should search engines (Google, Bing, etc.) be allowed to index this site?
# Set to 'True' to allow indexing (site will be discoverable in search engines).
# Set to 'False' to prevent indexing (site will not be indexed by search engines).
SEARCH_ENGINE_INDEXING=False
19 changes: 19 additions & 0 deletions core_directory/context_processors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
Context processor for SEO-related settings.

Provides the INDEXABLE setting to Django templates for use in meta tags and robots.txt.
"""

from django.conf import settings

def seo_settings(request):
"""
Add the INDEXABLE setting to the template context.

Args:
request: The current HttpRequest object.

Returns:
dict: A dictionary with the INDEXABLE setting.
"""
return {'INDEXABLE': settings.INDEXABLE}
18 changes: 18 additions & 0 deletions core_directory/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from semver import VersionInfo
from utils.sanitize import get_unique_sanitized_name
from utils.spdx import get_spdx_choices, get_spdx_license_url, validate_spdx
Expand Down Expand Up @@ -38,6 +39,12 @@ def __str__(self):
"""Return the vendors's name as its string representation."""
return f'{self.name}'

def get_absolute_url(self):
"""
Returns the canonical URL for this vendor.
"""
return reverse('vendor-detail', kwargs={'sanitized_name': self.sanitized_name})


class Library(UniqueSanitizedNameMixin):
"""Represents a library for a vendor."""
Expand Down Expand Up @@ -166,6 +173,17 @@ def get_license_url(self):
return get_spdx_license_url(self.spdx_license)
return None

def get_absolute_url(self):
"""
Returns the canonical URL for this core package version.
"""
return reverse('core-detail-vlnv', kwargs={
'vendor': self.project.vendor.sanitized_name,
'library': self.project.library.sanitized_name or '~',
'core': self.project.sanitized_name,
'version': self.sanitized_name,
})

def clean(self):
"""
Validates that the version string is a valid semantic version with all components.
Expand Down
68 changes: 68 additions & 0 deletions core_directory/sitemaps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
This module defines sitemap classes for the Django application.
Classes:
ProjectSitemap: Generates sitemap entries for all Project objects.
CorePackageSitemap: Generates sitemap entries for all CorePackage objects.
StaticViewSitemap: Generates sitemap entries for static views such as 'landing',
'core-package-list', and 'vendor-list'.
Each sitemap class specifies the change frequency and priority for its entries,
and provides methods to retrieve the items to be included in the sitemap.
"""

from django.contrib.sitemaps import Sitemap
from django.urls import reverse
from .models import CorePackage, Vendor

class VendorSitemap(Sitemap):
"""
Sitemap class for listing all Vendor objects.
Attributes:
changefreq (str): The frequency at which the vendor pages are likely to change.
priority (float): The priority of vendor pages in the sitemap.
Methods:
items(): Returns a queryset of all Vendor objects to be included in the sitemap.
"""
changefreq = "daily"
priority = 0.6

def items(self):
return Vendor.objects.all().order_by('sanitized_name', 'pk')

class CorePackageSitemap(Sitemap):
"""
Sitemap class for CorePackage objects.

This class defines the sitemap configuration for CorePackage entries,
specifying how frequently the sitemap should be updated and the priority
of these entries for search engines.

Attributes:
changefreq (str): Suggested frequency of changes for CorePackage objects ("daily").
priority (float): Priority of CorePackage objects in the sitemap (0.8).

Methods:
items(): Returns a queryset of all CorePackage objects to be included in the sitemap.
"""
changefreq = "daily"
priority = 0.8
def items(self):
return CorePackage.objects.all().order_by('vlnv_name')

class StaticViewSitemap(Sitemap):
"""
Sitemap for static views in the application.

Attributes:
priority (float): The priority of the sitemap entries.
changefreq (str): How frequently the pages are likely to change.

Methods:
items(): Returns a list of static view names to include in the sitemap.
location(item): Returns the URL for a given static view name.
"""
priority = 0.5
changefreq = "weekly"
def items(self):
return ['landing', 'core-package-list', 'vendor-list']
def location(self, item):
return reverse(item)
4 changes: 3 additions & 1 deletion core_directory/static/css/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
margin-bottom: 1.5rem;
}
.section-header {
cursor: pointer;
background: #f8f9fa;
font-weight: 500;
border-radius: 0.5rem 0.5rem 0 0;
Expand All @@ -30,6 +29,9 @@
padding: 0.75rem 1rem;
font-size: 1.1rem;
}
.section-card.collapsible > .section-header {
cursor: pointer;
}
.section-content {
display: none;
background: #fff;
Expand Down
Binary file added core_directory/static/img/og-default.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 38 additions & 14 deletions core_directory/static/js/expandable_section.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,42 @@
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.section-header').forEach(function(header) {
header.addEventListener('click', function() {
var content = header.nextElementSibling;
var btn = header.querySelector('.expand-toggle');
// Toggle visibility
if (content.style.display === 'none' || content.style.display === '') {
content.style.display = 'block';
// Optionally animate:
content.style.maxHeight = content.scrollHeight + "px";
btn.innerHTML = '<i class="bi bi-dash"></i>';
} else {
content.style.display = 'none';
btn.innerHTML = '<i class="bi bi-plus"></i>';
document.querySelectorAll('.section-card.collapsible').forEach(function(card) {
var header = card.querySelector('.section-header');
var content = card.querySelector('.section-content');
var toggle = header.querySelector('.expand-toggle');
var collapsed = card.getAttribute('data-collapsed') === 'true';

// Set initial state
if (collapsed) {
content.style.display = 'none';
if (toggle) toggle.innerHTML = '<i class="bi bi-plus"></i>';
} else {
content.style.display = 'block';
if (toggle) toggle.innerHTML = '<i class="bi bi-dash"></i>';
}

// Toggle function
function doToggle() {
var isOpen = content.style.display === 'block';
content.style.display = isOpen ? 'none' : 'block';
if (toggle) {
toggle.innerHTML = isOpen
? '<i class="bi bi-plus"></i>'
: '<i class="bi bi-dash"></i>';
}
}

// Click handler on header (but ignore if button was clicked)
header.addEventListener('click', function(e) {
if (e.target.closest('.expand-toggle')) return; // Don't toggle twice if button clicked
doToggle();
});

// Click handler on button (for keyboard accessibility)
if (toggle) {
toggle.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent header handler from firing
doToggle();
});
}
});
});
});
102 changes: 54 additions & 48 deletions core_directory/static/js/publish_core_from_github.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,60 +61,66 @@ document.addEventListener('DOMContentLoaded', function() {
let html = '';
if (obj.repo) {
html += `
<div class="card mb-3">
${getCardHeaderHtml("bi-github", "Repository details")}
<div class="card-body">
<h5 class="card-title">
<a href="${obj.repo.url}" target="_blank"><strong>${obj.repo.name}</strong></a>
<span class="fs-6">@${obj.parsed_version}</span>
</h5>
<p class="card-text">${obj.repo.description || ''}</p>
<p class="card-text">
<span class="me-3">⭐ ${obj.repo.stars}</span>
<span>🍴 ${obj.repo.forks}</span>
</p>
</div>
<div class="section-card mb-3">
<div class="section-header">
<i class="bi bi-github me-2"></i>
<strong>Repository details</strong>
</div>
<div class="section-content" style="display:block;">
<h3 class="h5">
<a href="${obj.repo.url}" target="_blank">${obj.repo.name}</a>
<span class="fs-6">@${obj.parsed_version}</span>
</h3>
<p>${obj.repo.description || ''}</p>
<p>
<span class="me-3">⭐ ${obj.repo.stars}</span>
<span>🍴 ${obj.repo.forks}</span>
</p>
</div>
</div>
`;
}
if (obj.core_files) {
html += `
<div class="card mb-3">
${getCardHeaderHtml("bi-boxes", "Select core")}
<div class="p-3">`;
if (obj.core_files.length > 0) {
const defaultCore = obj.core_files[0].core;
const defaultHasSig = obj.core_files[0].hasSig;
html += `
<div class="d-flex w-100 align-items-stretch gap-2 mb-3">
<div class="dropdown flex-grow-1">
<button class="btn btn-outline-primary dropdown-toggle w-100 d-flex justify-content-between align-items-center" type="button" id="coreDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span id="coreDropdownLabel" class="text-truncate">${defaultCore}</span>
</button>
<ul class="dropdown-menu w-100" aria-labelledby="coreDropdown">
${obj.core_files.map(f => `
<li>
<a class="dropdown-item core-dropdown-item d-flex align-items-center justify-content-between" href="#" data-core="${f.core}">
<span>${f.core}</span>
</a>
</li>
`).join('')}
</ul>
</div>
<button id="validate-core-btn" type="button" class="btn btn-primary">Validate</button>
<button id="publish-core-btn" type="button" class="btn btn-secondary" disabled>Publish</button>
<div class="section-card mb-3">
<div class="section-header">
<i class="bi bi-boxes me-2"></i>
<strong>Select core</strong>
</div>
<div class="section-content" style="display:block;">`;
if (obj.core_files.length > 0) {
const defaultCore = obj.core_files[0].core;
const defaultHasSig = obj.core_files[0].hasSig;
html += `
<div class="d-flex w-100 align-items-stretch gap-2 mb-3">
<div class="dropdown flex-grow-1">
<button class="btn btn-outline-primary dropdown-toggle w-100 d-flex justify-content-between align-items-center" type="button" id="coreDropdown" data-bs-toggle="dropdown" aria-expanded="false">
<span id="coreDropdownLabel" class="text-truncate">${defaultCore}</span>
</button>
<ul class="dropdown-menu w-100" aria-labelledby="coreDropdown">
${obj.core_files.map(f => `
<li>
<a class="dropdown-item core-dropdown-item d-flex align-items-center justify-content-between" href="#" data-core="${f.core}">
<span>${f.core}</span>
</a>
</li>
`).join('')}
</ul>
</div>
<div id="validate-result" style="margin-top:1em;"></div>
`;
} else {
html +=
`<div class="alert alert-warning">
<strong>No <code>.core</code> files found in the repository.</strong><br>
<span class="text-muted">
Note: Only <code>.core</code> files located in the root of the repository can be published.
</span>
</div>`;
}
<button id="validate-core-btn" type="button" class="btn btn-primary">Validate</button>
<button id="publish-core-btn" type="button" class="btn btn-secondary" disabled>Publish</button>
</div>
<div id="validate-result" style="margin-top:1em;"></div>
`;
} else {
html +=
`<div class="alert alert-warning">
<strong>No <code>.core</code> files found in the repository.</strong><br>
<span class="text-muted">
Note: Only <code>.core</code> files located in the root of the repository can be published.
</span>
</div>`;
}
html += `</div></div>`;
}
resultDiv.innerHTML = html;
Expand Down
12 changes: 10 additions & 2 deletions core_directory/templates/web_ui/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
<html lang="en">
<head>
<title>
{% block title %}FuseSoC Package Directory{% endblock %}
{% block title_suffix %}{% endblock %}
{% block title %}FuseSoC-PD{% endblock %} | FuseSoC Package Directory
</title>
<meta name="description" content="{% block meta_description %}Open source hardware cores and packages for FuseSoC.{% endblock %}">
<meta property="og:title" content="{% block og_title %}FuseSoC Package Directory{% endblock %}">
<meta property="og:description" content="{% block og_description %}Open source hardware cores and packages for FuseSoC.{% endblock %}">
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:type" content="website">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'img/og-default.jpg' %}">
<meta name="twitter:card" content="summary">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="canonical" href="{{ request.build_absolute_uri }}">
{% include "web_ui/includes/meta_robots.html" %}
<!-- Bootstrap & FontAwesome -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
Expand Down
Loading