Skip to content

Improve tags list page #34898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 30, 2025
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
41 changes: 17 additions & 24 deletions templates/repo/tag/list.tmpl
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -5,63 +5,57 @@
{{template "base/alert" .}} {{template "base/alert" .}}
{{template "repo/release_tag_header" .}} {{template "repo/release_tag_header" .}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
<div class="five wide column tw-flex tw-items-center">
{{.TagCount}} {{ctx.Locale.Tr "repo.release.tags"}} {{.TagCount}} {{ctx.Locale.Tr "repo.release.tags"}}
</div>
</h4> </h4>
{{$canReadReleases := $.Permission.CanRead ctx.Consts.RepoUnitTypeReleases}} {{$canReadReleases := $.Permission.CanRead ctx.Consts.RepoUnitTypeReleases}}
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ignore-dirty" method="get"> <form class="ignore-dirty" method="get">
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.tag_kind") "Tooltip" (ctx.Locale.Tr "search.tag_tooltip")}} {{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.tag_kind") "Tooltip" (ctx.Locale.Tr "search.tag_tooltip")}}
</form> </form>
</div> </div>
<div class="ui attached table segment"> <div class="ui attached segment tw-p-0">
{{if .Releases}} {{if .Releases}}
<table class="ui very basic striped fixed table single line" id="tags-table"> <div class="ui divided list" id="tags-table">
<tbody class="tag-list">
{{range $idx, $release := .Releases}} {{range $idx, $release := .Releases}}
<tr> <div class="item tag-list-row tw-p-4">
<td class="tag-list-row">
<h3 class="tag-list-row-title tw-mb-2"> <h3 class="tag-list-row-title tw-mb-2">
{{if $canReadReleases}} {{if $canReadReleases}}
<a class="tag-list-row-link tw-flex tw-items-center" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a> <a class="tag-list-row-link" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
{{else}} {{else}}
<a class="tag-list-row-link tw-flex tw-items-center" href="{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a> <a class="tag-list-row-link" href="{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
{{end}} {{end}}
</h3> </h3>
<div class="download tw-flex tw-items-center"> <div class="flex-text-block muted-links tw-gap-4 tw-flex-wrap">
{{if $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}} {{if $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
{{if .CreatedUnix}} {{if .CreatedUnix}}
<span class="tw-mr-2">{{svg "octicon-clock" 16 "tw-mr-1"}}{{DateUtils.TimeSince .CreatedUnix}}</span> <span class="flex-text-inline">{{svg "octicon-clock"}}{{DateUtils.TimeSince .CreatedUnix}}</span>
{{end}} {{end}}


<a class="tw-mr-2 tw-font-mono muted" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .Sha1}}</a> <a class="flex-text-inline tw-font-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit"}}{{ShortSha .Sha1}}</a>


{{if not $.DisableDownloadSourceArchives}} {{if not $.DisableDownloadSourceArchives}}
<a class="archive-link tw-mr-2 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-1"}}ZIP</a> <a class="archive-link flex-text-inline" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow">{{svg "octicon-file-zip"}}ZIP</a>
<a class="archive-link tw-mr-2 muted" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-1"}}TAR.GZ</a> <a class="archive-link flex-text-inline" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip"}}TAR.GZ</a>
{{end}} {{end}}


{{if (and $canReadReleases $.CanCreateRelease $release.IsTag)}} {{if (and $canReadReleases $.CanCreateRelease $release.IsTag)}}
<a class="tw-mr-2 muted" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.new_release"}}</a> <a class="flex-text-inline" href="{{$.RepoLink}}/releases/new?tag={{.TagName}}">{{svg "octicon-tag"}}{{ctx.Locale.Tr "repo.release.new_release"}}</a>
{{end}} {{end}}


{{if (and ($.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) $release.IsTag)}} {{if (and ($.Permission.CanWrite ctx.Consts.RepoUnitTypeCode) $release.IsTag)}}
<a class="ui delete-button tw-mr-2 muted" data-url="{{$.RepoLink}}/tags/delete" data-id="{{.ID}}"> <a class="flex-text-inline link-action" data-url="{{$.RepoLink}}/tags/delete?id={{.ID}}" data-modal-confirm="#confirm-delete-tag-modal">
{{svg "octicon-trash" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.delete_tag"}} {{svg "octicon-trash"}}{{ctx.Locale.Tr "repo.release.delete_tag"}}
</a> </a>
{{end}} {{end}}


{{if and $canReadReleases (not $release.IsTag)}} {{if and $canReadReleases (not $release.IsTag)}}
<a class="tw-mr-2 muted" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.detail"}}</a> <a class="flex-text-inline" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{svg "octicon-tag"}}{{ctx.Locale.Tr "repo.release.detail"}}</a>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
</td> </div>
</tr>
{{end}} {{end}}
</tbody> </div>
</table>
{{else}} {{else}}
{{if .NumTags}} {{if .NumTags}}
<p class="tw-p-4">{{ctx.Locale.Tr "no_results_found"}}</p> <p class="tw-p-4">{{ctx.Locale.Tr "no_results_found"}}</p>
Expand All @@ -73,9 +67,8 @@
</div> </div>


{{if $.Permission.CanWrite ctx.Consts.RepoUnitTypeCode}} {{if $.Permission.CanWrite ctx.Consts.RepoUnitTypeCode}}
<div class="ui g-modal-confirm delete modal"> <div id="confirm-delete-tag-modal" class="ui small modal">
<div class="header"> <div class="header">
{{svg "octicon-trash"}}
{{ctx.Locale.Tr "repo.release.delete_tag"}} {{ctx.Locale.Tr "repo.release.delete_tag"}}
</div> </div>
<div class="content"> <div class="content">
Expand Down
4 changes: 0 additions & 4 deletions web_src/css/repo/release-tag.css
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -91,10 +91,6 @@
border-bottom: none; border-bottom: none;
} }


#tags-table .tag-list-row {
padding: 8px 12px;
}

#tags-table .tag-list-row-title { #tags-table .tag-list-row-title {
font-size: 18px; font-size: 18px;
font-weight: var(--font-weight-normal); font-weight: var(--font-weight-normal);
Expand Down
42 changes: 29 additions & 13 deletions web_src/js/features/common-fetch-action.ts
Original file line number Original file line Diff line number Diff line change
@@ -1,7 +1,7 @@
import {request} from '../modules/fetch.ts'; import {request} from '../modules/fetch.ts';
import {hideToastsAll, showErrorToast} from '../modules/toast.ts'; import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
import {addDelegatedEventListener, submitEventSubmitter} from '../utils/dom.ts'; import {addDelegatedEventListener, createElementFromHTML, submitEventSubmitter} from '../utils/dom.ts';
import {confirmModal} from './comp/ConfirmModal.ts'; import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts';
import type {RequestOpts} from '../types.ts'; import type {RequestOpts} from '../types.ts';
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts'; import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';


Expand Down Expand Up @@ -111,28 +111,44 @@ export async function submitFormFetchAction(formEl: HTMLFormElement, formSubmitt
async function onLinkActionClick(el: HTMLElement, e: Event) { async function onLinkActionClick(el: HTMLElement, e: Event) {
// A "link-action" can post AJAX request to its "data-url" // A "link-action" can post AJAX request to its "data-url"
// Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading. // Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading.
// If the "link-action" has "data-modal-confirm" attribute, a confirm modal dialog will be shown before taking action. // If the "link-action" has "data-modal-confirm" attribute, a "confirm modal dialog" will be shown before taking action.
// Attribute "data-modal-confirm" can be a modal element by "#the-modal-id", or a string content for the modal dialog.
e.preventDefault(); e.preventDefault();
const url = el.getAttribute('data-url'); const url = el.getAttribute('data-url');
const doRequest = async () => { const doRequest = async () => {
if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but A doesn't have disabled attribute if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but "A" doesn't have the "disabled" attribute
await fetchActionDoRequest(el, url, {method: el.getAttribute('data-link-action-method') || 'POST'}); await fetchActionDoRequest(el, url, {method: el.getAttribute('data-link-action-method') || 'POST'});
if ('disabled' in el) el.disabled = false; if ('disabled' in el) el.disabled = false;
}; };


const modalConfirmContent = el.getAttribute('data-modal-confirm') || let elModal: HTMLElement | null = null;
el.getAttribute('data-modal-confirm-content') || ''; const dataModalConfirm = el.getAttribute('data-modal-confirm') || '';
if (!modalConfirmContent) { if (dataModalConfirm.startsWith('#')) {
await doRequest(); // eslint-disable-next-line unicorn/prefer-query-selector
return; elModal = document.getElementById(dataModalConfirm.substring(1));
} if (elModal) {

elModal = createElementFromHTML(elModal.outerHTML);
elModal.removeAttribute('id');
}
}
if (!elModal) {
const modalConfirmContent = dataModalConfirm || el.getAttribute('data-modal-confirm-content') || '';
if (modalConfirmContent) {
const isRisky = el.classList.contains('red') || el.classList.contains('negative'); const isRisky = el.classList.contains('red') || el.classList.contains('negative');
if (await confirmModal({ elModal = createConfirmModal({
header: el.getAttribute('data-modal-confirm-header') || '', header: el.getAttribute('data-modal-confirm-header') || '',
content: modalConfirmContent, content: modalConfirmContent,
confirmButtonColor: isRisky ? 'red' : 'primary', confirmButtonColor: isRisky ? 'red' : 'primary',
})) { });
}
}

if (!elModal) {
await doRequest();
return;
}

if (await confirmModal(elModal)) {
await doRequest(); await doRequest();
} }
} }
Expand Down
17 changes: 13 additions & 4 deletions web_src/js/features/comp/ConfirmModal.ts
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import {fomanticQuery} from '../../modules/fomantic/base.ts';


const {i18n} = window.config; const {i18n} = window.config;


export function confirmModal({header = '', content = '', confirmButtonColor = 'primary'} = {}): Promise<boolean> { type ConfirmModalOptions = {
return new Promise((resolve) => { header?: string;
content?: string;
confirmButtonColor?: 'primary' | 'red' | 'green' | 'blue';
}

export function createConfirmModal({header = '', content = '', confirmButtonColor = 'primary'}:ConfirmModalOptions = {}): HTMLElement {
const headerHtml = header ? `<div class="header">${htmlEscape(header)}</div>` : ''; const headerHtml = header ? `<div class="header">${htmlEscape(header)}</div>` : '';
const modal = createElementFromHTML(` return createElementFromHTML(`
<div class="ui g-modal-confirm modal"> <div class="ui g-modal-confirm modal">
${headerHtml} ${headerHtml}
<div class="content">${htmlEscape(content)}</div> <div class="content">${htmlEscape(content)}</div>
Expand All @@ -18,7 +23,11 @@ export function confirmModal({header = '', content = '', confirmButtonColor = 'p
</div> </div>
</div> </div>
`); `);
document.body.append(modal); }

export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise<boolean> {
if (!(modal instanceof HTMLElement)) modal = createConfirmModal(modal);
return new Promise((resolve) => {
const $modal = fomanticQuery(modal); const $modal = fomanticQuery(modal);
$modal.modal({ $modal.modal({
onApprove() { onApprove() {
Expand Down