mirror of
https://github.com/glanceapp/glance.git
synced 2025-06-21 18:31:24 +02:00
Update releases widget
This commit is contained in:
parent
3076593021
commit
0ce706e02e
@ -1332,6 +1332,19 @@ repositories:
|
|||||||
- dockerhub:nginx:stable-alpine
|
- dockerhub:nginx:stable-alpine
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To include prereleases you can specify the repository as an object and use the `include-prereleases` property:
|
||||||
|
|
||||||
|
**Note: This feature is currently only available for GitHub repositories.**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
repositories:
|
||||||
|
- gitlab:inkscape/inkscape
|
||||||
|
- repository: glanceapp/glance
|
||||||
|
include-prereleases: true
|
||||||
|
- codeberg:redict/redict
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##### `show-source-icon`
|
##### `show-source-icon`
|
||||||
Shows an icon of the source (GitHub/GitLab/Codeberg/Docker Hub) next to the repository name when set to `true`.
|
Shows an icon of the source (GitHub/GitLab/Codeberg/Docker Hub) next to the repository name when set to `true`.
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
package feed
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReleaseSource string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ReleaseSourceCodeberg ReleaseSource = "codeberg"
|
|
||||||
ReleaseSourceGithub ReleaseSource = "github"
|
|
||||||
ReleaseSourceGitlab ReleaseSource = "gitlab"
|
|
||||||
ReleaseSourceDockerHub ReleaseSource = "dockerhub"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReleaseRequest struct {
|
|
||||||
Source ReleaseSource
|
|
||||||
Repository string
|
|
||||||
Token *string
|
|
||||||
IncludeGithubPreReleases bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func FetchLatestReleases(requests []*ReleaseRequest) (AppReleases, error) {
|
|
||||||
job := newJob(fetchLatestReleaseTask, requests).withWorkers(20)
|
|
||||||
results, errs, err := workerPoolDo(job)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var failed int
|
|
||||||
|
|
||||||
releases := make(AppReleases, 0, len(requests))
|
|
||||||
|
|
||||||
for i := range results {
|
|
||||||
if errs[i] != nil {
|
|
||||||
failed++
|
|
||||||
slog.Error("Failed to fetch release", "source", requests[i].Source, "repository", requests[i].Repository, "error", errs[i])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
releases = append(releases, *results[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
if failed == len(requests) {
|
|
||||||
return nil, ErrNoContent
|
|
||||||
}
|
|
||||||
|
|
||||||
releases.SortByNewest()
|
|
||||||
|
|
||||||
if failed > 0 {
|
|
||||||
return releases, fmt.Errorf("%w: could not get %d releases", ErrPartialContent, failed)
|
|
||||||
}
|
|
||||||
|
|
||||||
return releases, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchLatestReleaseTask(request *ReleaseRequest) (*AppRelease, error) {
|
|
||||||
switch request.Source {
|
|
||||||
case ReleaseSourceCodeberg:
|
|
||||||
return fetchLatestCodebergRelease(request)
|
|
||||||
case ReleaseSourceGithub:
|
|
||||||
return fetchLatestGithubRelease(request)
|
|
||||||
case ReleaseSourceGitlab:
|
|
||||||
return fetchLatestGitLabRelease(request)
|
|
||||||
case ReleaseSourceDockerHub:
|
|
||||||
return fetchLatestDockerHubRelease(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("unsupported source")
|
|
||||||
}
|
|
@ -11,20 +11,21 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var releasesWidgetTemplate = mustParseTemplate("releases.html", "widget-base.html")
|
var releasesWidgetTemplate = mustParseTemplate("releases.html", "widget-base.html")
|
||||||
|
|
||||||
type releasesWidget struct {
|
type releasesWidget struct {
|
||||||
widgetBase `yaml:",inline"`
|
widgetBase `yaml:",inline"`
|
||||||
Releases appReleaseList `yaml:"-"`
|
Releases appReleaseList `yaml:"-"`
|
||||||
releaseRequests []*releaseRequest `yaml:"-"`
|
Repositories []*releaseRequest `yaml:"repositories"`
|
||||||
Repositories []string `yaml:"repositories"`
|
Token string `yaml:"token"`
|
||||||
Token string `yaml:"token"`
|
GitLabToken string `yaml:"gitlab-token"`
|
||||||
GitLabToken string `yaml:"gitlab-token"`
|
Limit int `yaml:"limit"`
|
||||||
Limit int `yaml:"limit"`
|
CollapseAfter int `yaml:"collapse-after"`
|
||||||
CollapseAfter int `yaml:"collapse-after"`
|
ShowSourceIcon bool `yaml:"show-source-icon"`
|
||||||
ShowSourceIcon bool `yaml:"show-source-icon"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (widget *releasesWidget) initialize() error {
|
func (widget *releasesWidget) initialize() error {
|
||||||
@ -38,51 +39,21 @@ func (widget *releasesWidget) initialize() error {
|
|||||||
widget.CollapseAfter = 5
|
widget.CollapseAfter = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repository := range widget.Repositories {
|
for i := range widget.Repositories {
|
||||||
parts := strings.SplitN(repository, ":", 2)
|
r := widget.Repositories[i]
|
||||||
var request *releaseRequest
|
|
||||||
if len(parts) == 1 {
|
|
||||||
request = &releaseRequest{
|
|
||||||
source: releaseSourceGithub,
|
|
||||||
repository: repository,
|
|
||||||
}
|
|
||||||
|
|
||||||
if widget.Token != "" {
|
if r.source == releaseSourceGithub && widget.Token != "" {
|
||||||
request.token = &widget.Token
|
r.token = &widget.Token
|
||||||
}
|
} else if r.source == releaseSourceGitlab && widget.GitLabToken != "" {
|
||||||
} else if len(parts) == 2 {
|
r.token = &widget.GitLabToken
|
||||||
if parts[0] == string(releaseSourceGitlab) {
|
|
||||||
request = &releaseRequest{
|
|
||||||
source: releaseSourceGitlab,
|
|
||||||
repository: parts[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
if widget.GitLabToken != "" {
|
|
||||||
request.token = &widget.GitLabToken
|
|
||||||
}
|
|
||||||
} else if parts[0] == string(releaseSourceDockerHub) {
|
|
||||||
request = &releaseRequest{
|
|
||||||
source: releaseSourceDockerHub,
|
|
||||||
repository: parts[1],
|
|
||||||
}
|
|
||||||
} else if parts[0] == string(releaseSourceCodeberg) {
|
|
||||||
request = &releaseRequest{
|
|
||||||
source: releaseSourceCodeberg,
|
|
||||||
repository: parts[1],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("invalid repository source " + parts[0])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.releaseRequests = append(widget.releaseRequests, request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (widget *releasesWidget) update(ctx context.Context) {
|
func (widget *releasesWidget) update(ctx context.Context) {
|
||||||
releases, err := fetchLatestReleases(widget.releaseRequests)
|
releases, err := fetchLatestReleases(widget.Repositories)
|
||||||
|
|
||||||
if !widget.canContinueUpdateAfterHandlingErr(err) {
|
if !widget.canContinueUpdateAfterHandlingErr(err) {
|
||||||
return
|
return
|
||||||
@ -133,9 +104,53 @@ func (r appReleaseList) sortByNewest() appReleaseList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type releaseRequest struct {
|
type releaseRequest struct {
|
||||||
source releaseSource
|
IncludePreleases bool `yaml:"include-prereleases"`
|
||||||
repository string
|
Repository string `yaml:"repository"`
|
||||||
token *string
|
|
||||||
|
source releaseSource
|
||||||
|
token *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *releaseRequest) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
type releaseRequestAlias releaseRequest
|
||||||
|
alias := (*releaseRequestAlias)(r)
|
||||||
|
var repository string
|
||||||
|
|
||||||
|
if err := node.Decode(&repository); err != nil {
|
||||||
|
if err := node.Decode(alias); err != nil {
|
||||||
|
return fmt.Errorf("could not umarshal repository into string or struct: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Repository == "" {
|
||||||
|
if repository == "" {
|
||||||
|
return errors.New("repository is required")
|
||||||
|
} else {
|
||||||
|
r.Repository = repository
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(repository, ":", 2)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
r.source = releaseSourceGithub
|
||||||
|
} else if len(parts) == 2 {
|
||||||
|
r.Repository = parts[1]
|
||||||
|
|
||||||
|
switch parts[0] {
|
||||||
|
case string(releaseSourceGithub):
|
||||||
|
r.source = releaseSourceGithub
|
||||||
|
case string(releaseSourceGitlab):
|
||||||
|
r.source = releaseSourceGitlab
|
||||||
|
case string(releaseSourceDockerHub):
|
||||||
|
r.source = releaseSourceDockerHub
|
||||||
|
case string(releaseSourceCodeberg):
|
||||||
|
r.source = releaseSourceCodeberg
|
||||||
|
default:
|
||||||
|
return errors.New("invalid source")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
|
func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
|
||||||
@ -152,7 +167,7 @@ func fetchLatestReleases(requests []*releaseRequest) (appReleaseList, error) {
|
|||||||
for i := range results {
|
for i := range results {
|
||||||
if errs[i] != nil {
|
if errs[i] != nil {
|
||||||
failed++
|
failed++
|
||||||
slog.Error("Failed to fetch release", "source", requests[i].source, "repository", requests[i].repository, "error", errs[i])
|
slog.Error("Failed to fetch release", "source", requests[i].source, "repository", requests[i].Repository, "error", errs[i])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +202,7 @@ func fetchLatestReleaseTask(request *releaseRequest) (*appRelease, error) {
|
|||||||
return nil, errors.New("unsupported source")
|
return nil, errors.New("unsupported source")
|
||||||
}
|
}
|
||||||
|
|
||||||
type githubReleaseLatestResponseJson struct {
|
type githubReleaseResponseJson struct {
|
||||||
TagName string `json:"tag_name"`
|
TagName string `json:"tag_name"`
|
||||||
PublishedAt string `json:"published_at"`
|
PublishedAt string `json:"published_at"`
|
||||||
HtmlUrl string `json:"html_url"`
|
HtmlUrl string `json:"html_url"`
|
||||||
@ -197,12 +212,17 @@ type githubReleaseLatestResponseJson struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
|
func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
|
||||||
httpRequest, err := http.NewRequest(
|
var requestURL string
|
||||||
"GET",
|
|
||||||
fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.repository),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if !request.IncludePreleases {
|
||||||
|
requestURL = fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", request.Repository)
|
||||||
|
} else {
|
||||||
|
requestURL = fmt.Sprintf("https://api.github.com/repos/%s/releases", request.Repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(requestURL)
|
||||||
|
|
||||||
|
httpRequest, err := http.NewRequest("GET", requestURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -211,14 +231,29 @@ func fetchLatestGithubRelease(request *releaseRequest) (*appRelease, error) {
|
|||||||
httpRequest.Header.Add("Authorization", "Bearer "+(*request.token))
|
httpRequest.Header.Add("Authorization", "Bearer "+(*request.token))
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := decodeJsonFromRequest[githubReleaseLatestResponseJson](defaultHTTPClient, httpRequest)
|
var response githubReleaseResponseJson
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
if !request.IncludePreleases {
|
||||||
|
response, err = decodeJsonFromRequest[githubReleaseResponseJson](defaultHTTPClient, httpRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responses, err := decodeJsonFromRequest[[]githubReleaseResponseJson](defaultHTTPClient, httpRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(responses) == 0 {
|
||||||
|
return nil, fmt.Errorf("no releases found for repository %s", request.Repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
response = responses[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return &appRelease{
|
return &appRelease{
|
||||||
Source: releaseSourceGithub,
|
Source: releaseSourceGithub,
|
||||||
Name: request.repository,
|
Name: request.Repository,
|
||||||
Version: normalizeVersionFormat(response.TagName),
|
Version: normalizeVersionFormat(response.TagName),
|
||||||
NotesUrl: response.HtmlUrl,
|
NotesUrl: response.HtmlUrl,
|
||||||
TimeReleased: parseRFC3339Time(response.PublishedAt),
|
TimeReleased: parseRFC3339Time(response.PublishedAt),
|
||||||
@ -242,10 +277,10 @@ const dockerHubSpecificTagURLFormat = "https://hub.docker.com/v2/namespaces/%s/r
|
|||||||
|
|
||||||
func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
|
func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
|
||||||
|
|
||||||
nameParts := strings.Split(request.repository, "/")
|
nameParts := strings.Split(request.Repository, "/")
|
||||||
|
|
||||||
if len(nameParts) > 2 {
|
if len(nameParts) > 2 {
|
||||||
return nil, fmt.Errorf("invalid repository name: %s", request.repository)
|
return nil, fmt.Errorf("invalid repository name: %s", request.Repository)
|
||||||
} else if len(nameParts) == 1 {
|
} else if len(nameParts) == 1 {
|
||||||
nameParts = []string{"library", nameParts[0]}
|
nameParts = []string{"library", nameParts[0]}
|
||||||
}
|
}
|
||||||
@ -278,7 +313,7 @@ func fetchLatestDockerHubRelease(request *releaseRequest) (*appRelease, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(response.Results) == 0 {
|
if len(response.Results) == 0 {
|
||||||
return nil, fmt.Errorf("no tags found for repository: %s", request.repository)
|
return nil, fmt.Errorf("no tags found for repository: %s", request.Repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
tag = &response.Results[0]
|
tag = &response.Results[0]
|
||||||
@ -331,7 +366,7 @@ func fetchLatestGitLabRelease(request *releaseRequest) (*appRelease, error) {
|
|||||||
"GET",
|
"GET",
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"https://gitlab.com/api/v4/projects/%s/releases/permalink/latest",
|
"https://gitlab.com/api/v4/projects/%s/releases/permalink/latest",
|
||||||
url.QueryEscape(request.repository),
|
url.QueryEscape(request.Repository),
|
||||||
),
|
),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
@ -350,7 +385,7 @@ func fetchLatestGitLabRelease(request *releaseRequest) (*appRelease, error) {
|
|||||||
|
|
||||||
return &appRelease{
|
return &appRelease{
|
||||||
Source: releaseSourceGitlab,
|
Source: releaseSourceGitlab,
|
||||||
Name: request.repository,
|
Name: request.Repository,
|
||||||
Version: normalizeVersionFormat(response.TagName),
|
Version: normalizeVersionFormat(response.TagName),
|
||||||
NotesUrl: response.Links.Self,
|
NotesUrl: response.Links.Self,
|
||||||
TimeReleased: parseRFC3339Time(response.ReleasedAt),
|
TimeReleased: parseRFC3339Time(response.ReleasedAt),
|
||||||
@ -368,7 +403,7 @@ func fetchLatestCodebergRelease(request *releaseRequest) (*appRelease, error) {
|
|||||||
"GET",
|
"GET",
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"https://codeberg.org/api/v1/repos/%s/releases/latest",
|
"https://codeberg.org/api/v1/repos/%s/releases/latest",
|
||||||
request.repository,
|
request.Repository,
|
||||||
),
|
),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
@ -383,7 +418,7 @@ func fetchLatestCodebergRelease(request *releaseRequest) (*appRelease, error) {
|
|||||||
|
|
||||||
return &appRelease{
|
return &appRelease{
|
||||||
Source: releaseSourceCodeberg,
|
Source: releaseSourceCodeberg,
|
||||||
Name: request.repository,
|
Name: request.Repository,
|
||||||
Version: normalizeVersionFormat(response.TagName),
|
Version: normalizeVersionFormat(response.TagName),
|
||||||
NotesUrl: response.HtmlUrl,
|
NotesUrl: response.HtmlUrl,
|
||||||
TimeReleased: parseRFC3339Time(response.PublishedAt),
|
TimeReleased: parseRFC3339Time(response.PublishedAt),
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
package widget
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"html/template"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/glanceapp/glance/internal/assets"
|
|
||||||
"github.com/glanceapp/glance/internal/feed"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Releases struct {
|
|
||||||
widgetBase `yaml:",inline"`
|
|
||||||
Releases feed.AppReleases `yaml:"-"`
|
|
||||||
releaseRequests []*feed.ReleaseRequest `yaml:"-"`
|
|
||||||
Repositories []string `yaml:"repositories"`
|
|
||||||
Token OptionalEnvString `yaml:"token"`
|
|
||||||
GitLabToken OptionalEnvString `yaml:"gitlab-token"`
|
|
||||||
Limit int `yaml:"limit"`
|
|
||||||
CollapseAfter int `yaml:"collapse-after"`
|
|
||||||
ShowSourceIcon bool `yaml:"show-source-icon"`
|
|
||||||
IncludeGithubPreReleases bool `yaml:"include-github-prereleases"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (widget *Releases) Initialize() error {
|
|
||||||
widget.withTitle("Releases").withCacheDuration(2 * time.Hour)
|
|
||||||
|
|
||||||
if widget.Limit <= 0 {
|
|
||||||
widget.Limit = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 {
|
|
||||||
widget.CollapseAfter = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokenAsString = widget.Token.String()
|
|
||||||
var gitLabTokenAsString = widget.GitLabToken.String()
|
|
||||||
|
|
||||||
for _, repository := range widget.Repositories {
|
|
||||||
parts := strings.SplitN(repository, ":", 2)
|
|
||||||
var request *feed.ReleaseRequest
|
|
||||||
if len(parts) == 1 {
|
|
||||||
request = &feed.ReleaseRequest{
|
|
||||||
Source: feed.ReleaseSourceGithub,
|
|
||||||
Repository: repository,
|
|
||||||
IncludeGithubPreReleases: widget.IncludeGithubPreReleases,
|
|
||||||
}
|
|
||||||
|
|
||||||
if widget.Token != "" {
|
|
||||||
request.Token = &tokenAsString
|
|
||||||
}
|
|
||||||
} else if len(parts) == 2 {
|
|
||||||
if parts[0] == string(feed.ReleaseSourceGitlab) {
|
|
||||||
request = &feed.ReleaseRequest{
|
|
||||||
Source: feed.ReleaseSourceGitlab,
|
|
||||||
Repository: parts[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
if widget.GitLabToken != "" {
|
|
||||||
request.Token = &gitLabTokenAsString
|
|
||||||
}
|
|
||||||
} else if parts[0] == string(feed.ReleaseSourceDockerHub) {
|
|
||||||
request = &feed.ReleaseRequest{
|
|
||||||
Source: feed.ReleaseSourceDockerHub,
|
|
||||||
Repository: parts[1],
|
|
||||||
}
|
|
||||||
} else if parts[0] == string(feed.ReleaseSourceCodeberg) {
|
|
||||||
request = &feed.ReleaseRequest{
|
|
||||||
Source: feed.ReleaseSourceCodeberg,
|
|
||||||
Repository: parts[1],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.New("invalid repository source " + parts[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
widget.releaseRequests = append(widget.releaseRequests, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (widget *Releases) Update(ctx context.Context) {
|
|
||||||
releases, err := feed.FetchLatestReleases(widget.releaseRequests)
|
|
||||||
|
|
||||||
if !widget.canContinueUpdateAfterHandlingErr(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(releases) > widget.Limit {
|
|
||||||
releases = releases[:widget.Limit]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range releases {
|
|
||||||
releases[i].SourceIconURL = widget.Providers.AssetResolver("icons/" + string(releases[i].Source) + ".svg")
|
|
||||||
}
|
|
||||||
|
|
||||||
widget.Releases = releases
|
|
||||||
}
|
|
||||||
|
|
||||||
func (widget *Releases) Render() template.HTML {
|
|
||||||
return widget.render(widget, assets.ReleasesTemplate)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user