mirror of
https://github.com/boostorg/website-v2.git
synced 2026-01-19 04:42:17 +00:00
display user icons on the homepage library spotlight refs #1658
Re-use the code written for the library detail page for displaying authors and maintainers on the homepage. To avoid duplicating code, moved all the necessary pieces to a mixin to be used by HomepageView and LibraryDetail, and adjusted it to work for both.
This commit is contained in:
@@ -4,7 +4,7 @@ import pytest
|
|||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
|
||||||
def test_homepage(library, version, tp):
|
def test_homepage(library, library_version, version, tp):
|
||||||
"""Ensure we can hit the homepage"""
|
"""Ensure we can hit the homepage"""
|
||||||
# Use any page that is named 'home' otherwise use /
|
# Use any page that is named 'home' otherwise use /
|
||||||
url = tp.reverse("home")
|
url = tp.reverse("home")
|
||||||
|
|||||||
24
ak/views.py
24
ak/views.py
@@ -9,18 +9,16 @@ from django.views.generic import TemplateView
|
|||||||
|
|
||||||
from core.calendar import extract_calendar_events, events_by_month, get_calendar
|
from core.calendar import extract_calendar_events, events_by_month, get_calendar
|
||||||
from libraries.constants import LATEST_RELEASE_URL_PATH_STR
|
from libraries.constants import LATEST_RELEASE_URL_PATH_STR
|
||||||
from libraries.models import Library
|
from libraries.mixins import ContributorMixin
|
||||||
from news.models import Entry
|
from news.models import Entry
|
||||||
from versions.models import Version
|
|
||||||
|
|
||||||
|
|
||||||
logger = structlog.get_logger()
|
logger = structlog.get_logger()
|
||||||
|
|
||||||
|
|
||||||
class HomepageView(TemplateView):
|
class HomepageView(ContributorMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
Our default homepage for temp-site. We expect you to not use this view
|
Define all the pieces that will be displayed on the home page
|
||||||
after you start working on your project.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template_name = "homepage.html"
|
template_name = "homepage.html"
|
||||||
@@ -28,9 +26,6 @@ class HomepageView(TemplateView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["entries"] = Entry.objects.published().order_by("-publish_at")[:3]
|
context["entries"] = Entry.objects.published().order_by("-publish_at")[:3]
|
||||||
latest_version = Version.objects.most_recent()
|
|
||||||
context["latest_version"] = latest_version
|
|
||||||
context["featured_library"] = self.get_featured_library(latest_version)
|
|
||||||
context["events"] = self.get_events()
|
context["events"] = self.get_events()
|
||||||
if context["events"]:
|
if context["events"]:
|
||||||
context["num_months"] = len(context["events"])
|
context["num_months"] = len(context["events"])
|
||||||
@@ -64,19 +59,6 @@ class HomepageView(TemplateView):
|
|||||||
|
|
||||||
return dict(sorted_events)
|
return dict(sorted_events)
|
||||||
|
|
||||||
def get_featured_library(self, latest_version):
|
|
||||||
library = Library.objects.filter(featured=True).first()
|
|
||||||
|
|
||||||
# If we don't have a featured library, return a random library
|
|
||||||
if not library:
|
|
||||||
library = (
|
|
||||||
Library.objects.filter(library_version__version=latest_version)
|
|
||||||
.order_by("?")
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
return library
|
|
||||||
|
|
||||||
|
|
||||||
class ForbiddenView(View):
|
class ForbiddenView(View):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import structlog
|
import structlog
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from django.db.models import Count, Exists, OuterRef
|
||||||
|
from django.db.models.functions import Lower
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@@ -7,7 +11,13 @@ from libraries.constants import (
|
|||||||
MASTER_RELEASE_URL_PATH_STR,
|
MASTER_RELEASE_URL_PATH_STR,
|
||||||
DEVELOP_RELEASE_URL_PATH_STR,
|
DEVELOP_RELEASE_URL_PATH_STR,
|
||||||
)
|
)
|
||||||
from libraries.models import Library
|
from libraries.models import (
|
||||||
|
Commit,
|
||||||
|
CommitAuthor,
|
||||||
|
CommitAuthorEmail,
|
||||||
|
Library,
|
||||||
|
LibraryVersion,
|
||||||
|
)
|
||||||
from versions.models import Version
|
from versions.models import Version
|
||||||
|
|
||||||
logger = structlog.get_logger()
|
logger = structlog.get_logger()
|
||||||
@@ -70,3 +80,171 @@ class BoostVersionMixin:
|
|||||||
)
|
)
|
||||||
# here we hack extra_context into the request so we can access for cookie checks
|
# here we hack extra_context into the request so we can access for cookie checks
|
||||||
request.extra_context = self.extra_context
|
request.extra_context = self.extra_context
|
||||||
|
|
||||||
|
|
||||||
|
class ContributorMixin:
|
||||||
|
"""Mixin to gather a list of all authors, maintainers, and
|
||||||
|
contributors without duplicates.
|
||||||
|
Uses the current Library if on the Library detail view,
|
||||||
|
otherwise grabs a featured library
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["latest_version"] = Version.objects.most_recent()
|
||||||
|
|
||||||
|
if hasattr(self, "object") and isinstance(self.object, Library):
|
||||||
|
library = self.object
|
||||||
|
try:
|
||||||
|
library_version = LibraryVersion.objects.get(
|
||||||
|
library=library, version=context["selected_version"]
|
||||||
|
)
|
||||||
|
except LibraryVersion.DoesNotExist:
|
||||||
|
return context
|
||||||
|
else:
|
||||||
|
library_version = self.get_featured_library()
|
||||||
|
context["featured_library"] = library_version
|
||||||
|
|
||||||
|
context["authors"] = self.get_related(library_version, "authors")
|
||||||
|
context["maintainers"] = self.get_related(
|
||||||
|
library_version,
|
||||||
|
"maintainers",
|
||||||
|
exclude_ids=[x.id for x in context["authors"]],
|
||||||
|
)
|
||||||
|
context["author_tag"] = self.get_author_tag(library_version)
|
||||||
|
exclude_maintainer_ids = [
|
||||||
|
x.commitauthor.id
|
||||||
|
for x in context["maintainers"]
|
||||||
|
if getattr(x.commitauthor, "id", None)
|
||||||
|
]
|
||||||
|
exclude_author_ids = [
|
||||||
|
x.commitauthor.id
|
||||||
|
for x in context["authors"]
|
||||||
|
if getattr(x.commitauthor, "id", None)
|
||||||
|
]
|
||||||
|
top_contributors_release = self.get_top_contributors(
|
||||||
|
library_version=library_version,
|
||||||
|
exclude=exclude_maintainer_ids + exclude_author_ids,
|
||||||
|
)
|
||||||
|
context["top_contributors_release_new"] = [
|
||||||
|
x for x in top_contributors_release if x.is_new
|
||||||
|
]
|
||||||
|
context["top_contributors_release_old"] = [
|
||||||
|
x for x in top_contributors_release if not x.is_new
|
||||||
|
]
|
||||||
|
exclude_top_contributor_ids = [x.id for x in top_contributors_release]
|
||||||
|
context["previous_contributors"] = self.get_previous_contributors(
|
||||||
|
library_version,
|
||||||
|
exclude=exclude_maintainer_ids
|
||||||
|
+ exclude_top_contributor_ids
|
||||||
|
+ exclude_author_ids,
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_featured_library(self):
|
||||||
|
"""Returns latest LibraryVersion associated with the featured Library"""
|
||||||
|
# If multiple are featured, pick one at random
|
||||||
|
latest_version = Version.objects.most_recent()
|
||||||
|
library = Library.objects.filter(featured=True).order_by("?").first()
|
||||||
|
|
||||||
|
# If we don't have a featured library, return a random library
|
||||||
|
if not library:
|
||||||
|
library = (
|
||||||
|
Library.objects.filter(library_version__version=latest_version)
|
||||||
|
.order_by("?")
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if not library:
|
||||||
|
return None
|
||||||
|
libversion = LibraryVersion.objects.filter(
|
||||||
|
library_id=library.id, version=latest_version
|
||||||
|
).first()
|
||||||
|
|
||||||
|
return libversion
|
||||||
|
|
||||||
|
def get_related(self, library_version, relation="maintainers", exclude_ids=None):
|
||||||
|
"""Get the maintainers|authors for the current LibraryVersion.
|
||||||
|
|
||||||
|
Also patches the CommitAuthor onto the user, if a matching email exists.
|
||||||
|
"""
|
||||||
|
if relation == "maintainers":
|
||||||
|
qs = library_version.maintainers.all()
|
||||||
|
elif relation == "authors":
|
||||||
|
qs = library_version.authors.all()
|
||||||
|
else:
|
||||||
|
raise ValueError("relation must be maintainers or authors.")
|
||||||
|
if exclude_ids:
|
||||||
|
qs = qs.exclude(id__in=exclude_ids)
|
||||||
|
qs = list(qs)
|
||||||
|
commit_authors = {
|
||||||
|
author_email.email: author_email
|
||||||
|
for author_email in CommitAuthorEmail.objects.annotate(
|
||||||
|
email_lower=Lower("email")
|
||||||
|
)
|
||||||
|
.filter(email_lower__in=[x.email.lower() for x in qs])
|
||||||
|
.select_related("author")
|
||||||
|
}
|
||||||
|
for user in qs:
|
||||||
|
if author_email := commit_authors.get(user.email.lower(), None):
|
||||||
|
user.commitauthor = author_email.author
|
||||||
|
else:
|
||||||
|
user.commitauthor = SimpleNamespace(
|
||||||
|
github_profile_url="",
|
||||||
|
avatar_url="",
|
||||||
|
display_name=f"{user.display_name}",
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_author_tag(self, library_version):
|
||||||
|
"""Format the authors for the author meta tag in the template."""
|
||||||
|
author_names = list(
|
||||||
|
library_version.library.authors.values_list("display_name", flat=True)
|
||||||
|
)
|
||||||
|
if len(author_names) > 1:
|
||||||
|
final_output = ", ".join(author_names[:-1]) + " and " + author_names[-1]
|
||||||
|
else:
|
||||||
|
final_output = author_names[0] if author_names else ""
|
||||||
|
|
||||||
|
return final_output
|
||||||
|
|
||||||
|
def get_top_contributors(self, library_version=None, exclude=None):
|
||||||
|
if library_version:
|
||||||
|
prev_versions = Version.objects.minor_versions().filter(
|
||||||
|
version_array__lt=library_version.version.cleaned_version_parts_int
|
||||||
|
)
|
||||||
|
qs = CommitAuthor.objects.filter(
|
||||||
|
commit__library_version=library_version
|
||||||
|
).annotate(
|
||||||
|
is_new=~Exists(
|
||||||
|
Commit.objects.filter(
|
||||||
|
author_id=OuterRef("id"),
|
||||||
|
library_version__in=LibraryVersion.objects.filter(
|
||||||
|
version__in=prev_versions, library=library_version.library
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
qs = CommitAuthor.objects.filter(
|
||||||
|
commit__library_version__library=self.object
|
||||||
|
)
|
||||||
|
if exclude:
|
||||||
|
qs = qs.exclude(id__in=exclude)
|
||||||
|
qs = qs.annotate(count=Count("commit")).order_by("-count")
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_previous_contributors(self, library_version, exclude=None):
|
||||||
|
library_versions = LibraryVersion.objects.filter(
|
||||||
|
library=library_version.library,
|
||||||
|
version__in=Version.objects.minor_versions().filter(
|
||||||
|
version_array__lt=library_version.version.cleaned_version_parts_int
|
||||||
|
),
|
||||||
|
)
|
||||||
|
qs = (
|
||||||
|
CommitAuthor.objects.filter(commit__library_version__in=library_versions)
|
||||||
|
.annotate(count=Count("commit"))
|
||||||
|
.order_by("-count")
|
||||||
|
)
|
||||||
|
if exclude:
|
||||||
|
qs = qs.exclude(id__in=exclude)
|
||||||
|
return qs
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from types import SimpleNamespace
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.db.models import F, Count, Exists, OuterRef, Prefetch
|
from django.db.models import F, Count, Prefetch
|
||||||
from django.db.models.functions import Lower
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
@@ -15,12 +13,9 @@ from core.githubhelper import GithubAPIClient
|
|||||||
from versions.models import Version
|
from versions.models import Version
|
||||||
|
|
||||||
from .constants import README_MISSING
|
from .constants import README_MISSING
|
||||||
from .mixins import VersionAlertMixin, BoostVersionMixin
|
from .mixins import VersionAlertMixin, BoostVersionMixin, ContributorMixin
|
||||||
from .models import (
|
from .models import (
|
||||||
Category,
|
Category,
|
||||||
Commit,
|
|
||||||
CommitAuthor,
|
|
||||||
CommitAuthorEmail,
|
|
||||||
Library,
|
Library,
|
||||||
LibraryVersion,
|
LibraryVersion,
|
||||||
)
|
)
|
||||||
@@ -208,7 +203,7 @@ class LibraryCategorized(LibraryListBase):
|
|||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name="dispatch")
|
@method_decorator(csrf_exempt, name="dispatch")
|
||||||
class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
class LibraryDetail(VersionAlertMixin, BoostVersionMixin, ContributorMixin, DetailView):
|
||||||
"""Display a single Library in insolation"""
|
"""Display a single Library in insolation"""
|
||||||
|
|
||||||
model = Library
|
model = Library
|
||||||
@@ -240,40 +235,7 @@ class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
|||||||
if library_version
|
if library_version
|
||||||
else self.object.github_url
|
else self.object.github_url
|
||||||
)
|
)
|
||||||
context["authors"] = self.get_related(library_version, "authors")
|
|
||||||
context["maintainers"] = self.get_related(
|
|
||||||
library_version,
|
|
||||||
"maintainers",
|
|
||||||
exclude_ids=[x.id for x in context["authors"]],
|
|
||||||
)
|
|
||||||
context["author_tag"] = self.get_author_tag()
|
|
||||||
exclude_maintainer_ids = [
|
|
||||||
x.commitauthor.id
|
|
||||||
for x in context["maintainers"]
|
|
||||||
if getattr(x.commitauthor, "id", None)
|
|
||||||
]
|
|
||||||
exclude_author_ids = [
|
|
||||||
x.commitauthor.id
|
|
||||||
for x in context["authors"]
|
|
||||||
if getattr(x.commitauthor, "id", None)
|
|
||||||
]
|
|
||||||
top_contributors_release = self.get_top_contributors(
|
|
||||||
version=context["selected_version"],
|
|
||||||
exclude=exclude_maintainer_ids + exclude_author_ids,
|
|
||||||
)
|
|
||||||
context["top_contributors_release_new"] = [
|
|
||||||
x for x in top_contributors_release if x.is_new
|
|
||||||
]
|
|
||||||
context["top_contributors_release_old"] = [
|
|
||||||
x for x in top_contributors_release if not x.is_new
|
|
||||||
]
|
|
||||||
exclude_top_contributor_ids = [x.id for x in top_contributors_release]
|
|
||||||
context["previous_contributors"] = self.get_previous_contributors(
|
|
||||||
context["selected_version"],
|
|
||||||
exclude=exclude_maintainer_ids
|
|
||||||
+ exclude_top_contributor_ids
|
|
||||||
+ exclude_author_ids,
|
|
||||||
)
|
|
||||||
# Populate the commit graphs
|
# Populate the commit graphs
|
||||||
context["commit_data_by_release"] = self.get_commit_data_by_release()
|
context["commit_data_by_release"] = self.get_commit_data_by_release()
|
||||||
context["dependency_diff"] = self.get_dependency_diff(library_version)
|
context["dependency_diff"] = self.get_dependency_diff(library_version)
|
||||||
@@ -309,17 +271,6 @@ class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
|||||||
for x in reversed(list(qs))
|
for x in reversed(list(qs))
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_author_tag(self):
|
|
||||||
"""Format the authors for the author meta tag in the template."""
|
|
||||||
authors = self.object.authors.all()
|
|
||||||
author_names = [author.display_name for author in authors]
|
|
||||||
if len(author_names) > 1:
|
|
||||||
final_output = ", ".join(author_names[:-1]) + " and " + author_names[-1]
|
|
||||||
else:
|
|
||||||
final_output = author_names[0] if author_names else ""
|
|
||||||
|
|
||||||
return final_output
|
|
||||||
|
|
||||||
def _prepare_commit_data(self, commit_data, data_type):
|
def _prepare_commit_data(self, commit_data, data_type):
|
||||||
commit_data_list = []
|
commit_data_list = []
|
||||||
for data in commit_data:
|
for data in commit_data:
|
||||||
@@ -345,84 +296,6 @@ class LibraryDetail(VersionAlertMixin, BoostVersionMixin, DetailView):
|
|||||||
# This should never happen because it should be caught in get_object
|
# This should never happen because it should be caught in get_object
|
||||||
return self.object.github_url
|
return self.object.github_url
|
||||||
|
|
||||||
def get_related(self, library_version, relation="maintainers", exclude_ids=None):
|
|
||||||
"""Get the maintainers|authors for the current LibraryVersion.
|
|
||||||
|
|
||||||
Also patches the CommitAuthor onto the user, if a matching email exists.
|
|
||||||
"""
|
|
||||||
if relation == "maintainers":
|
|
||||||
qs = library_version.maintainers.all()
|
|
||||||
elif relation == "authors":
|
|
||||||
qs = library_version.authors.all()
|
|
||||||
else:
|
|
||||||
raise ValueError("relation must be maintainers or authors.")
|
|
||||||
if exclude_ids:
|
|
||||||
qs = qs.exclude(id__in=exclude_ids)
|
|
||||||
qs = list(qs)
|
|
||||||
commit_authors = {
|
|
||||||
author_email.email: author_email
|
|
||||||
for author_email in CommitAuthorEmail.objects.annotate(
|
|
||||||
email_lower=Lower("email")
|
|
||||||
)
|
|
||||||
.filter(email_lower__in=[x.email.lower() for x in qs])
|
|
||||||
.select_related("author")
|
|
||||||
}
|
|
||||||
for user in qs:
|
|
||||||
if author_email := commit_authors.get(user.email.lower(), None):
|
|
||||||
user.commitauthor = author_email.author
|
|
||||||
else:
|
|
||||||
user.commitauthor = SimpleNamespace(
|
|
||||||
github_profile_url="",
|
|
||||||
avatar_url="",
|
|
||||||
display_name=f"{user.display_name}",
|
|
||||||
)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def get_top_contributors(self, version=None, exclude=None):
|
|
||||||
if version:
|
|
||||||
library_version = LibraryVersion.objects.get(
|
|
||||||
library=self.object, version=version
|
|
||||||
)
|
|
||||||
prev_versions = Version.objects.minor_versions().filter(
|
|
||||||
version_array__lt=version.cleaned_version_parts_int
|
|
||||||
)
|
|
||||||
qs = CommitAuthor.objects.filter(
|
|
||||||
commit__library_version=library_version
|
|
||||||
).annotate(
|
|
||||||
is_new=~Exists(
|
|
||||||
Commit.objects.filter(
|
|
||||||
author_id=OuterRef("id"),
|
|
||||||
library_version__in=LibraryVersion.objects.filter(
|
|
||||||
version__in=prev_versions, library=self.object
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
qs = CommitAuthor.objects.filter(
|
|
||||||
commit__library_version__library=self.object
|
|
||||||
)
|
|
||||||
if exclude:
|
|
||||||
qs = qs.exclude(id__in=exclude)
|
|
||||||
qs = qs.annotate(count=Count("commit")).order_by("-count")
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def get_previous_contributors(self, version, exclude=None):
|
|
||||||
library_versions = LibraryVersion.objects.filter(
|
|
||||||
library=self.object,
|
|
||||||
version__in=Version.objects.minor_versions().filter(
|
|
||||||
version_array__lt=version.cleaned_version_parts_int
|
|
||||||
),
|
|
||||||
)
|
|
||||||
qs = (
|
|
||||||
CommitAuthor.objects.filter(commit__library_version__in=library_versions)
|
|
||||||
.annotate(count=Count("commit"))
|
|
||||||
.order_by("-count")
|
|
||||||
)
|
|
||||||
if exclude:
|
|
||||||
qs = qs.exclude(id__in=exclude)
|
|
||||||
return qs
|
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self):
|
||||||
"""Get the version of Boost for the library we're currently looking at."""
|
"""Get the version of Boost for the library we're currently looking at."""
|
||||||
version_slug = self.kwargs.get("version_slug")
|
version_slug = self.kwargs.get("version_slug")
|
||||||
|
|||||||
@@ -133,29 +133,22 @@
|
|||||||
All Libraries <i class="fas fa-chevron-right text-sky-600 dark:text-sky-300 group-hover:text-orange dark:group-hover:text-orange"></i>
|
All Libraries <i class="fas fa-chevron-right text-sky-600 dark:text-sky-300 group-hover:text-orange dark:group-hover:text-orange"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="pb-2 mb-4 text-lg md:text-2xl capitalize border-b border-gray-400 text-orange dark:border-slate"><a href="{% url 'library-detail' library_slug=featured_library.slug version_slug=LATEST_RELEASE_URL_PATH_STR %}" class="link-header">{{ featured_library.name }}</a></h3>
|
<h3 class="pb-2 mb-4 text-lg md:text-2xl capitalize border-b border-gray-400 text-orange dark:border-slate"><a href="{% url 'library-detail' library_slug=featured_library.library.slug version_slug=LATEST_RELEASE_URL_PATH_STR %}" class="link-header">{{ featured_library.library.name }}</a></h3>
|
||||||
<span class="pb-1 mx-auto w-full text-sm md:text-base align-left">
|
<span class="pb-1 mx-auto w-full text-sm md:text-base align-left">
|
||||||
{{ featured_library.description }}
|
{{ featured_library.description }}
|
||||||
</span>
|
</span>
|
||||||
<p class="pb-1 mx-auto w-full text-xs md:text-sm align-left">{% if featured_library.first_boost_version %}Added in {{ featured_library.first_boost_version.display_name }}{% endif %}</p>
|
<p class="pb-1 mx-auto w-full text-xs md:text-sm align-left">{% if featured_library.first_boost_version %}Added in {{ featured_library.first_boost_version.display_name }}{% endif %}</p>
|
||||||
<div class="py-4">
|
<div class="py-4">
|
||||||
{% if featured_library.authors %}
|
{% if authors or maintainers %}
|
||||||
<div class="flex flex-wrap justify-start">
|
<div class="flex flex-wrap justify-start">
|
||||||
{% for author in featured_library.authors.all %}
|
<div class="grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-3 gap-x-2">
|
||||||
<div class="p-1 md:p-2 w-min text-center flex flex-col items-center justify-center">
|
{% for user in authors %}
|
||||||
<div class="bg-gray-300 dark:bg-slate rounded-lg w-[36px] h-[36px]">
|
{% avatar user=user commitauthor=user.commitauthor avatar_type="wide" contributor_label="Author" %}
|
||||||
{% if author.image %}
|
{% endfor %}
|
||||||
<img src="{{ author.image.url }}"
|
{% for user in maintainers %}
|
||||||
title="{{ author.display_name }}"
|
{% avatar user=user commitauthor=user.commitauthor avatar_type="wide" contributor_label="Maintainer" %}
|
||||||
alt="{{ author.display_name }}"
|
{% endfor %}
|
||||||
class="rounded-lg w-[36px] h-[36px]" />
|
|
||||||
{% else %}
|
|
||||||
<i class="text-3xl fas fa-user text-white dark:text-white/60" title="{{ author.display_name }}"></i>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<span class="text-xs">{{ author.display_name }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user