mirror of
https://github.com/go-gitea/gitea
synced 2025-07-04 01:27:20 +00:00
Improve tags list page (#34898)
This commit is contained in:
@ -5,9 +5,7 @@
|
|||||||
{{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">
|
||||||
@ -15,53 +13,49 @@
|
|||||||
{{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}}
|
<div class="item tag-list-row tw-p-4">
|
||||||
<tr>
|
<h3 class="tag-list-row-title tw-mb-2">
|
||||||
<td class="tag-list-row">
|
{{if $canReadReleases}}
|
||||||
<h3 class="tag-list-row-title tw-mb-2">
|
<a class="tag-list-row-link" href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
|
||||||
{{if $canReadReleases}}
|
{{else}}
|
||||||
<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}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
|
||||||
{{else}}
|
{{end}}
|
||||||
<a class="tag-list-row-link tw-flex tw-items-center" href="{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}" rel="nofollow">{{.TagName}}</a>
|
</h3>
|
||||||
{{end}}
|
<div class="flex-text-block muted-links tw-gap-4 tw-flex-wrap">
|
||||||
</h3>
|
{{if $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
||||||
<div class="download tw-flex tw-items-center">
|
{{if .CreatedUnix}}
|
||||||
{{if $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
<span class="flex-text-inline">{{svg "octicon-clock"}}{{DateUtils.TimeSince .CreatedUnix}}</span>
|
||||||
{{if .CreatedUnix}}
|
{{end}}
|
||||||
<span class="tw-mr-2">{{svg "octicon-clock" 16 "tw-mr-1"}}{{DateUtils.TimeSince .CreatedUnix}}</span>
|
|
||||||
{{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}}
|
</div>
|
||||||
</tbody>
|
|
||||||
</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>
|
||||||
@ -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">
|
||||||
|
@ -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);
|
||||||
|
@ -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';
|
||||||
|
|
||||||
@ -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('#')) {
|
||||||
|
// eslint-disable-next-line unicorn/prefer-query-selector
|
||||||
|
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');
|
||||||
|
elModal = createConfirmModal({
|
||||||
|
header: el.getAttribute('data-modal-confirm-header') || '',
|
||||||
|
content: modalConfirmContent,
|
||||||
|
confirmButtonColor: isRisky ? 'red' : 'primary',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!elModal) {
|
||||||
await doRequest();
|
await doRequest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isRisky = el.classList.contains('red') || el.classList.contains('negative');
|
if (await confirmModal(elModal)) {
|
||||||
if (await confirmModal({
|
|
||||||
header: el.getAttribute('data-modal-confirm-header') || '',
|
|
||||||
content: modalConfirmContent,
|
|
||||||
confirmButtonColor: isRisky ? 'red' : 'primary',
|
|
||||||
})) {
|
|
||||||
await doRequest();
|
await doRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,29 @@ 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 = {
|
||||||
|
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>` : '';
|
||||||
|
return createElementFromHTML(`
|
||||||
|
<div class="ui g-modal-confirm modal">
|
||||||
|
${headerHtml}
|
||||||
|
<div class="content">${htmlEscape(content)}</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="ui cancel button">${svg('octicon-x')} ${htmlEscape(i18n.modal_cancel)}</button>
|
||||||
|
<button class="ui ${confirmButtonColor} ok button">${svg('octicon-check')} ${htmlEscape(i18n.modal_confirm)}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise<boolean> {
|
||||||
|
if (!(modal instanceof HTMLElement)) modal = createConfirmModal(modal);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const headerHtml = header ? `<div class="header">${htmlEscape(header)}</div>` : '';
|
|
||||||
const modal = createElementFromHTML(`
|
|
||||||
<div class="ui g-modal-confirm modal">
|
|
||||||
${headerHtml}
|
|
||||||
<div class="content">${htmlEscape(content)}</div>
|
|
||||||
<div class="actions">
|
|
||||||
<button class="ui cancel button">${svg('octicon-x')} ${htmlEscape(i18n.modal_cancel)}</button>
|
|
||||||
<button class="ui ${confirmButtonColor} ok button">${svg('octicon-check')} ${htmlEscape(i18n.modal_confirm)}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
document.body.append(modal);
|
|
||||||
const $modal = fomanticQuery(modal);
|
const $modal = fomanticQuery(modal);
|
||||||
$modal.modal({
|
$modal.modal({
|
||||||
onApprove() {
|
onApprove() {
|
||||||
|
Reference in New Issue
Block a user