feat(ui): responsive releases list (#11080)

## Changes

I've made releases list more usable on narrow viewports while trying keep the layout unchanged for desktop viewports.

To support these changes large amount of Tailwind classes were converted to regular CSS to be applied conditionally via `@media`. While it was possible to just adjust the Tailwind classes to achieve the same behavior, there's a positive effect which is that the repeating HTML of releases generated by template's range is much less verbose and contains fewer long duplicated lines of Tailwind classes.

## Preview

### Desktop

Not much changed, but the dot between tag and release name is no more.

|Before|After|
|-|-|
|![bd](/attachments/a87bf050-03ba-4035-8a0e-7ab4f6e877a2)|![2d](/attachments/e0c2e052-01c6-4078-b06e-f8ef7b803690)|

### Mobile

|Before|After|
|-|-|
|![bm](/attachments/f63a606f-f3f8-435d-8378-0979e93cf7bd)|![2m](/attachments/0cb6086d-1def-4a86-bb0e-4131e50aae3b)|

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11080
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Reviewed-by: Beowulf <beowulf@beocode.eu>
This commit is contained in:
0ko 2026-02-07 19:15:14 +01:00
parent f24a97f719
commit 7e4619df83
7 changed files with 243 additions and 141 deletions

View file

@ -7,35 +7,38 @@
<ul id="release-list"> <ul id="release-list">
{{range $idx, $info := .Releases}} {{range $idx, $info := .Releases}}
{{$release := $info.Release}} {{$release := $info.Release}}
<li class="ui grid"> {{if ne $idx 0}}
<div class="ui four wide column meta"> <div class="divider only-mobile"></div>
{{end}}
<li>
<div class="meta">
<a class="muted" href="{{if not (and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{$release.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "tw-mr-1"}}{{$release.TagName}}</a> <a class="muted" href="{{if not (and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{$release.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "tw-mr-1"}}{{$release.TagName}}</a>
{{if and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode)}} {{if and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode)}}
<a class="muted tw-font-mono" href="{{$.RepoLink}}/src/commit/{{$release.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha $release.Sha1}}</a> <a class="muted tw-font-mono" href="{{$.RepoLink}}/src/commit/{{$release.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha $release.Sha1}}</a>
{{template "repo/branch_dropdown" dict "root" $ "release" $release}} {{template "repo/branch_dropdown" dict "root" $ "release" $release}}
{{end}} {{end}}
</div> </div>
<div class="ui twelve wide column detail"> <div class="release-title-wrap">
<div class="tw-flex tw-items-center tw-justify-between tw-flex-wrap tw-mb-2"> <h4>
<h4 class="release-list-title tw-break-anywhere"> <a href="{{$.RepoLink}}/releases/tag/{{$release.TagName | PathEscapeSegments}}">{{$release.Title}}</a>
<a href="{{$.RepoLink}}/releases/tag/{{$release.TagName | PathEscapeSegments}}">{{$release.Title}}</a> {{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "tw-flex"}}
{{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "tw-flex"}} {{if $release.IsDraft}}
{{if $release.IsDraft}} <span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span>
<span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span> {{else if $release.IsPrerelease}}
{{else if $release.IsPrerelease}} <span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span>
<span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span> {{else if (not $release.IsTag)}}
{{else if (not $release.IsTag)}} <span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span>
<span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span> {{end}}
{{end}} </h4>
</h4> <div>
<div> {{if and $.CanCreateRelease (not $release.IsTag)}}
{{if and $.CanCreateRelease (not $release.IsTag)}} <a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{$release.TagName | PathEscapeSegments}}" rel="nofollow">
<a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{$release.TagName | PathEscapeSegments}}" rel="nofollow"> {{svg "octicon-pencil"}}
{{svg "octicon-pencil"}} </a>
</a> {{end}}
{{end}}
</div>
</div> </div>
</div>
<div class="detail">
<p class="text grey"> <p class="text grey">
<span class="author"> <span class="author">
{{if $release.OriginalAuthor}} {{if $release.OriginalAuthor}}
@ -59,15 +62,17 @@
{{end}} {{end}}
</p> </p>
{{template "repo/tag/verification_line" (dict "ctxData" $ "release" $release)}} {{template "repo/tag/verification_line" (dict "ctxData" $ "release" $release)}}
<div class="markup desc"> {{if $release.RenderedNote}}
{{$release.RenderedNote}} <div class="markup desc">
</div> {{$release.RenderedNote}}
</div>
{{end}}
{{$hasReleaseAttachment := gt (len $release.Attachments) 0}} {{$hasReleaseAttachment := gt (len $release.Attachments) 0}}
{{$hasArchiveLinks := and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) (not $release.HideArchiveLinks) ($.Permission.CanRead $.UnitTypeCode)}} {{$hasArchiveLinks := and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) (not $release.HideArchiveLinks) ($.Permission.CanRead $.UnitTypeCode)}}
{{if or $hasArchiveLinks $hasReleaseAttachment}} {{if or $hasArchiveLinks $hasReleaseAttachment}}
<div class="divider"></div> <div class="divider not-mobile"></div>
<details class="download" {{if eq $idx 0}}open{{end}}> <details class="download" {{if eq $idx 0}}open{{end}}>
<summary class="tw-my-4"> <summary>
{{ctx.Locale.Tr "repo.release.downloads"}} {{ctx.Locale.Tr "repo.release.downloads"}}
</summary> </summary>
<ul class="list"> <ul class="list">
@ -76,7 +81,7 @@
<a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow" type="application/zip"> <a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow" type="application/zip">
{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP) {{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)
</a> </a>
<div class="tw-mr-1"> <div class="tw-mr-2">
<span class="text grey">{{ctx.Locale.TrPluralString .Release.ArchiveDownloadCount.Zip "release.n_downloads" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.Zip)}}</span> <span class="text grey">{{ctx.Locale.TrPluralString .Release.ArchiveDownloadCount.Zip "release.n_downloads" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.Zip)}}</span>
</div> </div>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}"> <span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
@ -87,7 +92,7 @@
<a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow" type="application/gzip"> <a class="archive-link tw-flex-1 flex-text-inline tw-font-bold" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow" type="application/gzip">
{{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ) {{svg "octicon-file-zip" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)
</a> </a>
<div class="tw-mr-1"> <div class="tw-mr-2">
<span class="text grey">{{ctx.Locale.TrPluralString .Release.ArchiveDownloadCount.TarGz "release.n_downloads" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.TarGz)}}</span> <span class="text grey">{{ctx.Locale.TrPluralString .Release.ArchiveDownloadCount.TarGz "release.n_downloads" (ctx.Locale.PrettyNumber .Release.ArchiveDownloadCount.TarGz)}}</span>
</div> </div>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}"> <span data-tooltip-content="{{ctx.Locale.Tr "repo.release.system_generated"}}">
@ -104,7 +109,7 @@
</a> </a>
</li> </li>
{{else}} {{else}}
<li class="max-sm:tw-flex-col max-sm:tw-gap-2"> <li class="attachment">
<a class="tw-flex-1 flex-text-inline tw-font-bold" target="_blank" rel="nofollow" href="{{.DownloadURL}}" download> <a class="tw-flex-1 flex-text-inline tw-font-bold" target="_blank" rel="nofollow" href="{{.DownloadURL}}" download>
{{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}} {{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}}
</a> </a>
@ -117,7 +122,6 @@
</ul> </ul>
</details> </details>
{{end}} {{end}}
<div class="dot"></div>
</div> </div>
</li> </li>
{{end}} {{end}}

View file

@ -14,15 +14,15 @@
{{template "shared/search/combo" dict "Value" .Keyword}} {{template "shared/search/combo" dict "Value" .Keyword}}
</form> </form>
{{end}} {{end}}
<div class="button-sequence release-list-buttons"> <div class="small button-sequence release-list-buttons">
{{if .EnableFeed}} {{if .EnableFeed}}
<a class="ui small button" href="{{.RepoLink}}/{{if .PageIsTagList}}tags{{else}}releases{{end}}.rss"> <a class="secondary button" href="{{.RepoLink}}/{{if .PageIsTagList}}tags{{else}}releases{{end}}.rss">
{{svg "octicon-rss" 16}} {{svg "octicon-rss" 16}}
<label class="not-mobile">{{ctx.Locale.Tr "rss_feed"}}</label> <label class="not-mobile">{{ctx.Locale.Tr "rss_feed"}}</label>
</a> </a>
{{end}} {{end}}
{{if and (not .PageIsTagList) .CanCreateRelease}} {{if and (not .PageIsTagList) .CanCreateRelease}}
<a class="ui small primary button" href="{{$.RepoLink}}/releases/new"> <a class="primary button" href="{{$.RepoLink}}/releases/new">
{{ctx.Locale.Tr "repo.release.new_release"}} {{ctx.Locale.Tr "repo.release.new_release"}}
</a> </a>
{{end}} {{end}}

View file

@ -1,3 +1,6 @@
// Copyright 2024-2026 The Forgejo Authors
// SPDX-License-Identifier: GPL-3.0-or-later
// @watch start // @watch start
// models/repo/attachment.go // models/repo/attachment.go
// modules/structs/attachment.go // modules/structs/attachment.go
@ -16,7 +19,7 @@ import {validate_form} from './shared/forms.ts';
test.use({user: 'user2'}); test.use({user: 'user2'});
test.describe('Releases', () => { test.describe('Releases', () => {
test('External Release Attachments', async ({page, isMobile}) => { test('External release attachments', async ({page, isMobile}) => {
test.skip(isMobile); test.skip(isMobile);
// Click "New release" // Click "New release"
@ -102,6 +105,44 @@ test.describe('Releases', () => {
await expect(page.locator('input[name=title]')).toHaveValue('v2.0'); await expect(page.locator('input[name=title]')).toHaveValue('v2.0');
}); });
test('UI reaction to lengthy UGC', async ({page, viewport, isMobile}) => {
await page.goto('/user2/repo2/releases/new');
await page.locator('input[name=tag_name]').pressSequentially('2.0');
await page.locator('input[name=title]').pressSequentially('v'.repeat(200));
await page.locator('textarea[name=content]').pressSequentially('v'.repeat(200)); // Description
// Submit form. Mobile Chrome can't press the button in Playwright (not a Forgejo
// bug). Work around this by pressing Enter on submit button
await page.getByRole('button', {name: 'Publish release'}).press('Enter');
// Check widths of UI elements
await page.goto('/user2/repo2/releases');
const release = page.locator('#release-list > li:has(a[href$="/tag/2.0"])');
// Release entry should be less than viewport
expect((await release.boundingBox()).width).toBeLessThan(viewport.width);
if (isMobile) {
const metaWidth = (await release.locator('.meta').boundingBox()).width;
const titleWidth = (await release.locator('.release-title-wrap').boundingBox()).width;
const detailsWidth = (await release.locator('.detail').boundingBox()).width;
// In row layout they all should be similar to the viewport length, accounting
// for 8px margins on each side
expect(metaWidth).toBeCloseTo(viewport.width - 16, 0);
expect(titleWidth).toBeCloseTo(viewport.width - 16, 0);
expect(detailsWidth).toBeCloseTo(viewport.width - 16, 0);
// They also should all be all same width
expect(metaWidth).toBe(titleWidth);
expect(titleWidth).toBe(detailsWidth);
} else {
// Left and right columns should be less than 25% and 75% of viewport width
// But on wide screens there's a lot of additional emptiness, so we can't
// match columns' width against the viewport, only make sure they fit
expect((await release.locator('.meta').boundingBox()).width).toBeLessThan(viewport.width * 0.75);
expect((await release.locator('.release-title-wrap').boundingBox()).width).toBeLessThan(viewport.width * 0.75);
expect((await release.locator('.detail').boundingBox()).width).toBeLessThan(viewport.width * 0.75);
}
});
test.afterEach(async ({page}) => { test.afterEach(async ({page}) => {
// Delete release // Delete release
const response = await page.goto('/user2/repo2/releases/edit/2.0'); const response = await page.goto('/user2/repo2/releases/edit/2.0');

View file

@ -72,9 +72,9 @@ func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, ver
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
labelText := htmlDoc.doc.Find("#release-list > li .detail .label").First().Text() labelText := htmlDoc.doc.Find("#release-list > li > .release-title-wrap .label").First().Text()
assert.Equal(t, label, labelText) assert.Equal(t, label, labelText)
titleText := htmlDoc.doc.Find("#release-list > li .detail h4 a").First().Text() titleText := htmlDoc.doc.Find("#release-list > li > .release-title-wrap h4 a").First().Text()
assert.Equal(t, version, titleText) assert.Equal(t, version, titleText)
// Check release count in the counter on the Release/Tag switch, as well as that the tab is highlighted // Check release count in the counter on the Release/Tag switch, as well as that the tab is highlighted
@ -255,13 +255,13 @@ func TestViewReleaseListNoLogin(t *testing.T) {
rsp := MakeRequest(t, req, http.StatusOK) rsp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, rsp.Body) htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid") releases := htmlDoc.Find("ul#release-list > li")
assert.Equal(t, 5, releases.Length()) assert.Equal(t, 5, releases.Length())
links := make([]string, 0, 5) links := make([]string, 0, 5)
commitsToMain := make([]string, 0, 5) commitsToMain := make([]string, 0, 5)
releases.Each(func(i int, s *goquery.Selection) { releases.Each(func(i int, s *goquery.Selection) {
link, exist := s.Find(".release-list-title a").Attr("href") link, exist := s.Find(".release-title-wrap h4 a").Attr("href")
if !exist { if !exist {
return return
} }
@ -311,12 +311,12 @@ func TestViewReleaseListLogin(t *testing.T) {
rsp := session.MakeRequest(t, req, http.StatusOK) rsp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, rsp.Body) htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid") releases := htmlDoc.Find("ul#release-list > li")
assert.Equal(t, 3, releases.Length()) assert.Equal(t, 3, releases.Length())
links := make([]string, 0, 5) links := make([]string, 0, 5)
releases.Each(func(i int, s *goquery.Selection) { releases.Each(func(i int, s *goquery.Selection) {
link, exist := s.Find(".release-list-title a").Attr("href") link, exist := s.Find(".release-title-wrap h4 a").Attr("href")
if !exist { if !exist {
return return
} }
@ -342,12 +342,12 @@ func TestViewReleaseListKeyword(t *testing.T) {
rsp := session.MakeRequest(t, req, http.StatusOK) rsp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, rsp.Body) htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid") releases := htmlDoc.Find("ul#release-list > li")
assert.Equal(t, 1, releases.Length()) assert.Equal(t, 1, releases.Length())
links := make([]string, 0, 5) links := make([]string, 0, 5)
releases.Each(func(i int, s *goquery.Selection) { releases.Each(func(i int, s *goquery.Selection) {
link, exist := s.Find(".release-list-title a").Attr("href") link, exist := s.Find(".release-title-wrap h4 a").Attr("href")
if !exist { if !exist {
return return
} }
@ -370,7 +370,7 @@ func TestViewReleaseListKeywordNoPagination(t *testing.T) {
rsp := session.MakeRequest(t, req, http.StatusOK) rsp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, rsp.Body) htmlDoc := NewHTMLParser(t, rsp.Body)
releases := htmlDoc.Find("#release-list li.ui.grid") releases := htmlDoc.Find("ul#release-list > li")
assert.Equal(t, 1, releases.Length()) assert.Equal(t, 1, releases.Length())
pagination := htmlDoc.Find("div.pagination") pagination := htmlDoc.Find("div.pagination")

View file

@ -51,11 +51,11 @@ func TestTagViewWithoutRelease(t *testing.T) {
assert.False(t, releaseLink.HasClass("active")) assert.False(t, releaseLink.HasClass("active"))
// Test that the title is displayed // Test that the title is displayed
releaseTitle := strings.TrimSpace(htmlDoc.Find("h4.release-list-title > a").Text()) releaseTitle := strings.TrimSpace(htmlDoc.Find(".release-title-wrap h4 > a").Text())
assert.Equal(t, "no-release", releaseTitle) assert.Equal(t, "no-release", releaseTitle)
// Test that there is no "Stable" link // Test that there is no "Stable" link
htmlDoc.AssertElement(t, "h4.release-list-title > span.ui.green.label", false) htmlDoc.AssertElement(t, ".release-title-wrap h4 > span.ui.green.label", false)
// Ensure that there is no "Edit" button // Ensure that there is no "Edit" button
htmlDoc.AssertElement(t, ".detail a.muted > svg.octicon-pencil", false) htmlDoc.AssertElement(t, ".detail a.muted > svg.octicon-pencil", false)
@ -148,7 +148,7 @@ func TestCreateNewTagProtected(t *testing.T) {
req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName()) req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName())
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
tagsTab := htmlDoc.Find(".release-list-title") tagsTab := htmlDoc.Find(".release-title-wrap h4")
assert.Contains(t, tagsTab.Text(), "force update v2") assert.Contains(t, tagsTab.Text(), "force update v2")
}) })
}) })
@ -180,7 +180,7 @@ func TestSyncRepoTags(t *testing.T) {
req := NewRequestf(t, "GET", "/%s/releases/tag/v2", repo.FullName()) req := NewRequestf(t, "GET", "/%s/releases/tag/v2", repo.FullName())
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
tagsTab := htmlDoc.Find(".release-list-title") tagsTab := htmlDoc.Find(".release-title-wrap h4")
assert.Contains(t, tagsTab.Text(), "this is an annotated tag") assert.Contains(t, tagsTab.Text(), "this is an annotated tag")
} }

View file

@ -1750,31 +1750,6 @@ details.repo-search-result summary::marker {
border-bottom: 1px solid var(--color-warning-border); border-bottom: 1px solid var(--color-warning-border);
} }
.repository .release-tag-name .ui.label.isSigned,
.repository .release-list-title .ui.label.isSigned {
padding: 0 0.5em;
box-shadow: none;
}
.repository .release-tag-name .ui.label.isSigned .avatar,
.repository .release-list-title .ui.label.isSigned .avatar {
margin-left: .5rem;
}
.repository .release-tag-name .ui.label.isSigned.isVerified,
.repository .release-list-title .ui.label.isSigned.isVerified {
border: 1px solid var(--color-success-border);
background-color: var(--color-success-bg);
color: var(--color-success-text);
}
.repository .release-tag-name .ui.label.isSigned.isWarning,
.repository .release-list-title .ui.label.isSigned.isWarning {
border: 1px solid var(--color-warning-border);
background-color: var(--color-warning-bg);
color: var(--color-warning-text);
}
.repository .segment.reactions.dropdown .menu, .repository .segment.reactions.dropdown .menu,
.repository .select-reaction.dropdown .menu { .repository .select-reaction.dropdown .menu {
right: 0 !important; right: 0 !important;

View file

@ -1,75 +1,145 @@
.repository.releases #release-list { #release-list {
margin-top: 12px; margin-top: 10px; /* Overriding browser default for <ul>, same value as divider */
padding-top: 12px; padding-left: 0; /* Unset browser default */
padding-left: 0;
} }
.repository.releases #release-list .release-list-title { #release-list > li {
.meta {
display: flex;
gap: 1em;
}
.detail .desc {
margin-block-start: 1rem;
}
}
/* Column layout (desktop) */
@media (min-width: 768px) {
#release-list > li {
display: grid;
grid-template-columns: 25% 75%;
justify-content: end;
.meta {
grid-column: 1;
grid-row: 1 / span 2;
flex-direction: column;
text-align: right;
/* Align contents of meta column with release name */
padding-top: 11px;
}
.detail {
grid-column: 2;
grid-row: 2;
padding-block-end: 1.5rem;
}
:is(.detail, .release-title-wrap) {
/* Line separating columns on wide screen */
border-left: 1px solid var(--color-secondary);
padding-inline-start: 1rem;
margin-inline-start: 1rem;
}
.detail .download summary {
margin-block: 1rem;
}
}
}
/* Row layout (mobile) */
@media (max-width: 767.98px) {
#release-list > li {
display: flex;
flex-direction: column;
.meta {
order: 1;
flex-direction: row;
align-items: center;
margin-block-end: 0.75rem;
}
.detail {
order: 2;
}
.detail .text {
/* Same as `summary` */
margin-block-end: 0.75rem;
}
.detail .download summary {
margin-block: 0.75rem;
}
}
.release-list-search {
order: 2 !important;
}
.release-list-buttons {
margin-left: auto;
}
}
#release-list .release-title-wrap {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
padding-block-end: 0.5rem;
}
#release-list .release-title-wrap h4 {
font-size: 2rem; font-size: 2rem;
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.25em; gap: 0.25em;
margin: 0; margin: 0;
} overflow-wrap: anywhere;
.repository.releases #release-list > li .meta {
padding-top: 25px;
position: relative;
text-align: right;
display: flex;
flex-direction: column;
gap: 1em;
}
.repository.releases #release-list > li .detail {
padding-bottom: 20px;
border-left: 1px solid var(--color-secondary);
} }
.repository.releases #release-list > li .detail .author img { .repository.releases #release-list > li .detail .author img {
margin-bottom: 2px; /* the legacy trick to align the avatar vertically, no better solution at the moment */ margin-bottom: 2px; /* the legacy trick to align the avatar vertically, no better solution at the moment */
} }
.repository.releases #release-list > li .detail .download .list { /* List of downloads */
#release-list > li .detail .download .list {
/* Override <ul> default */
padding-left: 0; padding-left: 0;
/* Compensate emptiness that opener provides when <details> is closed */
padding-block-end: 1rem;
hr {
height: 8px;
margin: 0;
}
li {
background: var(--color-light);
border: 1px solid var(--color-secondary);
border-top: none;
display: flex;
justify-content: space-between;
padding: 8px;
}
:is(li:first-child, .start-gap + hr + li) {
border-top: 1px solid var(--color-secondary);
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
:is(li:last-child, .start-gap) {
border-bottom: 1px solid var(--color-secondary);
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
} }
.repository.releases #release-list > li .detail .download .list li { @media (max-width: 640px) {
background: var(--color-light); #release-list > li .detail .download .list .attachment {
border: 1px solid var(--color-secondary); flex-direction: column;
border-top: none; gap: 0.5rem;
display: flex; }
justify-content: space-between;
padding: 8px;
}
.repository.releases #release-list > li .detail .download .list :is(li:first-child, .start-gap + hr + li) {
border-top: 1px solid var(--color-secondary);
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.repository.releases #release-list > li .detail .download .list :is(li:last-child, .start-gap) {
border-bottom: 1px solid var(--color-secondary);
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
.repository.releases #release-list > li .detail .download .list hr {
height: 8px;
margin: 0;
}
.repository.releases #release-list > li .detail .dot {
width: 10px;
height: 10px;
background-color: var(--color-secondary-dark-3);
position: absolute;
left: -5.5px;
top: 30px;
border-radius: var(--border-radius-full);
border: 2.5px solid var(--color-body);
} }
.repository.tags #tags-table .tag { .repository.tags #tags-table .tag {
@ -85,10 +155,6 @@
min-width: 500px; min-width: 500px;
} }
.repository.new.release .target #tag-name {
margin-top: -4px;
}
.repository.new.release .target .at { .repository.new.release .target .at {
margin-left: -5px; margin-left: -5px;
margin-right: 5px; margin-right: 5px;
@ -99,15 +165,6 @@
padding-bottom: 10px; padding-bottom: 10px;
} }
@media (max-width: 767.98px) {
.release-list-search {
order: 2 !important;
}
.release-list-buttons {
margin-left: auto;
}
}
.repository.new.release .field .attachment_edit { .repository.new.release .field .attachment_edit {
max-width: 48em; max-width: 48em;
} }
@ -128,3 +185,28 @@
.ui.ui.ui.tag-label.IsRelease:hover { .ui.ui.ui.tag-label.IsRelease:hover {
border-color: var(--color-primary-dark-1); border-color: var(--color-primary-dark-1);
} }
.repository .release-tag-name .ui.label.isSigned,
.repository .release-title-wrap .ui.label.isSigned {
padding: 0 0.5em;
box-shadow: none;
}
.repository .release-tag-name .ui.label.isSigned .avatar,
.repository .release-title-wrap .ui.label.isSigned .avatar {
margin-left: .5rem;
}
.repository .release-tag-name .ui.label.isSigned.isVerified,
.repository .release-title-wrap .ui.label.isSigned.isVerified {
border: 1px solid var(--color-success-border);
background-color: var(--color-success-bg);
color: var(--color-success-text);
}
.repository .release-tag-name .ui.label.isSigned.isWarning,
.repository .release-title-wrap .ui.label.isSigned.isWarning {
border: 1px solid var(--color-warning-border);
background-color: var(--color-warning-bg);
color: var(--color-warning-text);
}