mirror of
https://github.com/go-gitea/gitea.git
synced 2024-12-28 00:17:00 +00:00
Refactor LabelEdit (#32752)
And fix a regression: https://github.com/go-gitea/gitea/pull/30053#discussion_r1874405470 Major changes: * rewrite without jquery * remove the "delete modal", using "link-action" is good enough * merge "new modal" and "edit modal"
This commit is contained in:
parent
a78a466383
commit
96d3a03a08
@ -1,15 +1,13 @@
|
|||||||
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings labels")}}
|
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings labels")}}
|
||||||
<div class="org-setting-content">
|
<div class="org-setting-content">
|
||||||
<div class="tw-flex tw-items-center">
|
<div class="tw-flex tw-items-center">
|
||||||
<div class="tw-flex-1">
|
<div class="tw-flex-1">
|
||||||
{{ctx.Locale.Tr "org.settings.labels_desc"}}
|
{{ctx.Locale.Tr "org.settings.labels_desc"}}
|
||||||
</div>
|
</div>
|
||||||
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
{{template "repo/issue/labels/label_new" .}}
|
{{template "repo/issue/labels/label_list" .}}
|
||||||
{{template "repo/issue/labels/label_list" .}}
|
{{template "repo/issue/labels/label_edit_modal" .}}
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/issue/labels/edit_delete_label" .}}
|
|
||||||
{{template "org/settings/layout_footer" .}}
|
{{template "org/settings/layout_footer" .}}
|
||||||
|
|
||||||
|
@ -8,15 +8,11 @@
|
|||||||
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
|
||||||
{{template "repo/issue/labels/label_new" .}}
|
|
||||||
{{end}}
|
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
{{template "repo/issue/labels/label_list" .}}
|
{{template "repo/issue/labels/label_list" .}}
|
||||||
</div>
|
</div>
|
||||||
|
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
||||||
|
{{template "repo/issue/labels/label_edit_modal" .}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
|
|
||||||
{{template "repo/issue/labels/edit_delete_label" .}}
|
|
||||||
{{end}}
|
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
@ -1,22 +1,13 @@
|
|||||||
<div class="ui g-modal-confirm delete modal">
|
<div class="ui small modal" id="issue-label-edit-modal"
|
||||||
<div class="header">
|
data-current-page-link="{{$.Link}}"{{/*will be used to construct "new label" and "edit label" URLs*/}}
|
||||||
{{svg "octicon-trash"}}
|
data-text-new-label="{{ctx.Locale.Tr "repo.issues.new_label"}}"
|
||||||
{{ctx.Locale.Tr "repo.issues.label_deletion"}}
|
data-text-edit-label="{{ctx.Locale.Tr "repo.issues.label_modify"}}"
|
||||||
</div>
|
>
|
||||||
|
<div class="header"></div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p>{{ctx.Locale.Tr "repo.issues.label_deletion_desc"}}</p>
|
<form class="ui form ignore-dirty" method="post">
|
||||||
</div>
|
|
||||||
{{template "base/modal_actions_confirm" .}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui small edit-label modal">
|
|
||||||
<div class="header">
|
|
||||||
{{ctx.Locale.Tr "repo.issues.label_modify"}}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<form class="ui edit-label form ignore-dirty" action="{{$.Link}}/edit" method="post">
|
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<input id="label-modal-id" name="id" type="hidden">
|
<input name="id" type="hidden">
|
||||||
<div class="required field">
|
<div class="required field">
|
||||||
<label for="name">{{ctx.Locale.Tr "repo.issues.label_title"}}</label>
|
<label for="name">{{ctx.Locale.Tr "repo.issues.label_title"}}</label>
|
||||||
<div class="ui small input">
|
<div class="ui small input">
|
@ -27,6 +27,8 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<ul class="issue-label-list">
|
<ul class="issue-label-list">
|
||||||
|
{{$canEditLabel := and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
|
||||||
|
{{$canEditLabel = or $canEditLabel $.PageIsOrgSettingsLabels}}
|
||||||
{{range .Labels}}
|
{{range .Labels}}
|
||||||
<li class="item">
|
<li class="item">
|
||||||
<div class="label-title">
|
<div class="label-title">
|
||||||
@ -43,12 +45,16 @@
|
|||||||
<div class="label-operation tw-flex">
|
<div class="label-operation tw-flex">
|
||||||
{{template "repo/issue/labels/label_archived" .}}
|
{{template "repo/issue/labels/label_archived" .}}
|
||||||
<div class="tw-flex tw-ml-auto">
|
<div class="tw-flex tw-ml-auto">
|
||||||
{{if and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
|
{{if $canEditLabel}}
|
||||||
<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} {{if gt .ArchivedUnix 0}}data-is-archived{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{ctx.Locale.Tr "repo.issues.label_edit"}}</a>
|
<a class="edit-label-button" href="#"
|
||||||
<a class="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
|
data-label-id="{{.ID}}" data-label-name="{{.Name}}" data-label-color="{{.Color}}"
|
||||||
{{else if $.PageIsOrgSettingsLabels}}
|
data-label-exclusive="{{.Exclusive}}" data-label-is-archived="{{gt .ArchivedUnix 0}}"
|
||||||
<a class="edit-label-button" href="#" data-id="{{.ID}}" data-title="{{.Name}}" {{if .Exclusive}}data-exclusive{{end}} {{if gt .ArchivedUnix 0}}data-is-archived{{end}} data-num-issues="{{.NumIssues}}" data-description="{{.Description}}" data-color={{.Color}}>{{svg "octicon-pencil"}} {{ctx.Locale.Tr "repo.issues.label_edit"}}</a>
|
data-label-num-issues="{{.NumIssues}}" data-label-description="{{.Description}}"
|
||||||
<a class="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
|
>{{svg "octicon-pencil"}} {{ctx.Locale.Tr "repo.issues.label_edit"}}</a>
|
||||||
|
<a class="link-action" href="#" data-url="{{$.Link}}/delete?id={{.ID}}"
|
||||||
|
data-modal-confirm-header="{{ctx.Locale.Tr "repo.issues.label_deletion"}}"
|
||||||
|
data-modal-confirm-content="{{ctx.Locale.Tr "repo.issues.label_deletion_desc"}}"
|
||||||
|
>{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
<div class="ui small new-label modal">
|
|
||||||
<div class="header">
|
|
||||||
{{ctx.Locale.Tr "repo.issues.new_label"}}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<form class="ui new-label form ignore-dirty" action="{{$.Link}}/new" method="post">
|
|
||||||
{{.CsrfTokenHtml}}
|
|
||||||
<div class="required field">
|
|
||||||
<label for="name">{{ctx.Locale.Tr "repo.issues.label_title"}}</label>
|
|
||||||
<div class="ui small input">
|
|
||||||
<input class="label-name-input" name="title" placeholder="{{ctx.Locale.Tr "repo.issues.new_label_placeholder"}}" autofocus required maxlength="50">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field label-exclusive-input-field">
|
|
||||||
<div class="ui checkbox">
|
|
||||||
<input class="label-exclusive-input" name="exclusive" type="checkbox">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.issues.label_exclusive"}}</label>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<small class="desc">{{ctx.Locale.Tr "repo.issues.label_exclusive_desc"}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="description">{{ctx.Locale.Tr "repo.issues.label_description"}}</label>
|
|
||||||
<div class="ui small fluid input">
|
|
||||||
<input class="label-desc-input" name="description" placeholder="{{ctx.Locale.Tr "repo.issues.new_label_desc_placeholder"}}" maxlength="200">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field color-field">
|
|
||||||
<label for="color">{{ctx.Locale.Tr "repo.issues.label_color"}}</label>
|
|
||||||
<div class="js-color-picker-input column">
|
|
||||||
<input name="color" value="#70c24a" placeholder="#c320f6" required maxlength="7">
|
|
||||||
{{template "repo/issue/label_precolors"}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<button class="ui cancel button">
|
|
||||||
{{svg "octicon-x"}}
|
|
||||||
{{ctx.Locale.Tr "cancel"}}
|
|
||||||
</button>
|
|
||||||
<button class="ui primary ok button">
|
|
||||||
{{svg "octicon-check"}}
|
|
||||||
{{ctx.Locale.Tr "repo.issues.create_label"}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -2061,17 +2061,6 @@ td .commit-summary {
|
|||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-label.modal .form .column,
|
|
||||||
.new-label.modal .form .column {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-label.modal .form .buttons,
|
|
||||||
.new-label.modal .form .buttons {
|
|
||||||
margin-left: auto;
|
|
||||||
padding-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-table {
|
.stats-table {
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -12,5 +12,5 @@ export function initCommonOrganization() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
initCompLabelEdit('.organization.settings.labels');
|
initCompLabelEdit('.page-content.organization.settings.labels');
|
||||||
}
|
}
|
||||||
|
@ -1,96 +1,81 @@
|
|||||||
import $ from 'jquery';
|
import {toggleElem} from '../../utils/dom.ts';
|
||||||
|
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
||||||
|
|
||||||
function isExclusiveScopeName(name) {
|
function nameHasScope(name: string): boolean {
|
||||||
return /.*[^/]\/[^/].*/.test(name);
|
return /.*[^/]\/[^/].*/.test(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateExclusiveLabelEdit(form) {
|
export function initCompLabelEdit(pageSelector: string) {
|
||||||
const nameInput = document.querySelector(`${form} .label-name-input`);
|
const pageContent = document.querySelector<HTMLElement>(pageSelector);
|
||||||
const exclusiveField = document.querySelector(`${form} .label-exclusive-input-field`);
|
if (!pageContent) return;
|
||||||
const exclusiveCheckbox = document.querySelector(`${form} .label-exclusive-input`);
|
|
||||||
const exclusiveWarning = document.querySelector(`${form} .label-exclusive-warning`);
|
|
||||||
|
|
||||||
if (isExclusiveScopeName(nameInput.value)) {
|
// for guest view, the modal is not available, the "labels" are read-only
|
||||||
exclusiveField?.classList.remove('muted');
|
const elModal = pageContent.querySelector<HTMLElement>('#issue-label-edit-modal');
|
||||||
exclusiveField?.removeAttribute('aria-disabled');
|
if (!elModal) return;
|
||||||
if (exclusiveCheckbox.checked && exclusiveCheckbox.getAttribute('data-exclusive-warn')) {
|
|
||||||
exclusiveWarning?.classList.remove('tw-hidden');
|
const elLabelId = elModal.querySelector<HTMLInputElement>('input[name="id"]');
|
||||||
} else {
|
const elNameInput = elModal.querySelector<HTMLInputElement>('.label-name-input');
|
||||||
exclusiveWarning?.classList.add('tw-hidden');
|
const elExclusiveField = elModal.querySelector('.label-exclusive-input-field');
|
||||||
}
|
const elExclusiveInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-input');
|
||||||
} else {
|
const elExclusiveWarning = elModal.querySelector('.label-exclusive-warning');
|
||||||
exclusiveField?.classList.add('muted');
|
const elIsArchivedField = elModal.querySelector('.label-is-archived-input-field');
|
||||||
exclusiveField?.setAttribute('aria-disabled', 'true');
|
const elIsArchivedInput = elModal.querySelector<HTMLInputElement>('.label-is-archived-input');
|
||||||
exclusiveWarning?.classList.add('tw-hidden');
|
const elDescInput = elModal.querySelector<HTMLInputElement>('.label-desc-input');
|
||||||
|
const elColorInput = elModal.querySelector<HTMLInputElement>('.js-color-picker-input input');
|
||||||
|
|
||||||
|
const syncModalUi = () => {
|
||||||
|
const hasScope = nameHasScope(elNameInput.value);
|
||||||
|
elExclusiveField.classList.toggle('disabled', !hasScope);
|
||||||
|
const showExclusiveWarning = hasScope && elExclusiveInput.checked && elModal.hasAttribute('data-need-warn-exclusive');
|
||||||
|
toggleElem(elExclusiveWarning, showExclusiveWarning);
|
||||||
|
if (!hasScope) elExclusiveInput.checked = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showLabelEditModal = (btn:HTMLElement) => {
|
||||||
|
// the "btn" should contain the label's attributes by its `data-label-xxx` attributes
|
||||||
|
const form = elModal.querySelector<HTMLFormElement>('form');
|
||||||
|
elLabelId.value = btn.getAttribute('data-label-id') || '';
|
||||||
|
elNameInput.value = btn.getAttribute('data-label-name') || '';
|
||||||
|
elIsArchivedInput.checked = btn.getAttribute('data-label-is-archived') === 'true';
|
||||||
|
elExclusiveInput.checked = btn.getAttribute('data-label-exclusive') === 'true';
|
||||||
|
elDescInput.value = btn.getAttribute('data-label-description') || '';
|
||||||
|
elColorInput.value = btn.getAttribute('data-label-color') || '';
|
||||||
|
elColorInput.dispatchEvent(new Event('input', {bubbles: true})); // trigger the color picker
|
||||||
|
|
||||||
|
// if label id exists: "edit label" mode; otherwise: "new label" mode
|
||||||
|
const isEdit = Boolean(elLabelId.value);
|
||||||
|
|
||||||
|
// if a label was not exclusive but has issues, then it should warn user if it will become exclusive
|
||||||
|
const numIssues = parseInt(btn.getAttribute('data-label-num-issues') || '0');
|
||||||
|
elModal.toggleAttribute('data-need-warn-exclusive', !elExclusiveInput.checked && numIssues > 0);
|
||||||
|
elModal.querySelector('.header').textContent = isEdit ? elModal.getAttribute('data-text-edit-label') : elModal.getAttribute('data-text-new-label');
|
||||||
|
|
||||||
|
const curPageLink = elModal.getAttribute('data-current-page-link');
|
||||||
|
form.action = isEdit ? `${curPageLink}/edit` : `${curPageLink}/new`;
|
||||||
|
toggleElem(elIsArchivedField, isEdit);
|
||||||
|
syncModalUi();
|
||||||
|
fomanticQuery(elModal).modal({
|
||||||
|
onApprove() {
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
form.submit();
|
||||||
|
},
|
||||||
|
}).modal('show');
|
||||||
|
};
|
||||||
|
|
||||||
|
elModal.addEventListener('input', () => syncModalUi());
|
||||||
|
|
||||||
|
// theoretically, if the modal exists, the "new label" button should also exist, just in case it doesn't, use "?."
|
||||||
|
const elNewLabel = pageContent.querySelector<HTMLElement>('.ui.button.new-label');
|
||||||
|
elNewLabel?.addEventListener('click', () => showLabelEditModal(elNewLabel));
|
||||||
|
|
||||||
|
const elEditLabelButtons = pageContent.querySelectorAll<HTMLElement>('.edit-label-button');
|
||||||
|
for (const btn of elEditLabelButtons) {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
showLabelEditModal(btn);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initCompLabelEdit(selector) {
|
|
||||||
if (!$(selector).length) return;
|
|
||||||
|
|
||||||
// Create label
|
|
||||||
$('.new-label.button').on('click', () => {
|
|
||||||
updateExclusiveLabelEdit('.new-label');
|
|
||||||
$('.new-label.modal').modal({
|
|
||||||
onApprove() {
|
|
||||||
const form = document.querySelector('.new-label.form');
|
|
||||||
if (!form.checkValidity()) {
|
|
||||||
form.reportValidity();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$('.new-label.form').trigger('submit');
|
|
||||||
},
|
|
||||||
}).modal('show');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Edit label
|
|
||||||
$('.edit-label-button').on('click', function () {
|
|
||||||
$('#label-modal-id').val($(this).data('id'));
|
|
||||||
|
|
||||||
const $nameInput = $('.edit-label .label-name-input');
|
|
||||||
$nameInput.val($(this).data('title'));
|
|
||||||
|
|
||||||
const $isArchivedCheckbox = $('.edit-label .label-is-archived-input');
|
|
||||||
$isArchivedCheckbox[0].checked = this.hasAttribute('data-is-archived');
|
|
||||||
|
|
||||||
const $exclusiveCheckbox = $('.edit-label .label-exclusive-input');
|
|
||||||
$exclusiveCheckbox[0].checked = this.hasAttribute('data-exclusive');
|
|
||||||
// Warn when label was previously not exclusive and used in issues
|
|
||||||
$exclusiveCheckbox.data('exclusive-warn',
|
|
||||||
$(this).data('num-issues') > 0 &&
|
|
||||||
(!this.hasAttribute('data-exclusive') || !isExclusiveScopeName($nameInput.val())));
|
|
||||||
updateExclusiveLabelEdit('.edit-label');
|
|
||||||
|
|
||||||
$('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
|
|
||||||
|
|
||||||
const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
|
|
||||||
colorInput.value = this.getAttribute('data-color');
|
|
||||||
colorInput.dispatchEvent(new Event('input', {bubbles: true}));
|
|
||||||
|
|
||||||
$('.edit-label.modal').modal({
|
|
||||||
onApprove() {
|
|
||||||
const form = document.querySelector('.edit-label.form');
|
|
||||||
if (!form.checkValidity()) {
|
|
||||||
form.reportValidity();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$('.edit-label.form').trigger('submit');
|
|
||||||
},
|
|
||||||
}).modal('show');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.new-label .label-name-input').on('input', () => {
|
|
||||||
updateExclusiveLabelEdit('.new-label');
|
|
||||||
});
|
|
||||||
$('.new-label .label-exclusive-input').on('change', () => {
|
|
||||||
updateExclusiveLabelEdit('.new-label');
|
|
||||||
});
|
|
||||||
$('.edit-label .label-name-input').on('input', () => {
|
|
||||||
updateExclusiveLabelEdit('.edit-label');
|
|
||||||
});
|
|
||||||
$('.edit-label .label-exclusive-input').on('change', () => {
|
|
||||||
updateExclusiveLabelEdit('.edit-label');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -43,7 +43,7 @@ export function initRepository() {
|
|||||||
initRepoCommentFormAndSidebar();
|
initRepoCommentFormAndSidebar();
|
||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
initCompLabelEdit('.repository.labels');
|
initCompLabelEdit('.page-content.repository.labels');
|
||||||
initRepoMilestone();
|
initRepoMilestone();
|
||||||
initRepoNew();
|
initRepoNew();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user