Skip to content

Commit

Permalink
Refactor LabelEdit (#32752)
Browse files Browse the repository at this point in the history
And fix a regression:
#30053 (comment)

Major changes:

* rewrite without jquery
* remove the "delete modal", using "link-action" is good enough
* merge "new modal" and "edit modal"
  • Loading branch information
wxiaoguang authored Dec 8, 2024
1 parent a78a466 commit 96d3a03
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 180 deletions.
24 changes: 11 additions & 13 deletions templates/org/settings/labels.tmpl
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings labels")}}
<div class="org-setting-content">
<div class="tw-flex tw-items-center">
<div class="tw-flex-1">
{{ctx.Locale.Tr "org.settings.labels_desc"}}
</div>
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
</div>
<div class="divider"></div>
{{template "repo/issue/labels/label_new" .}}
{{template "repo/issue/labels/label_list" .}}
</div>
{{template "repo/issue/labels/edit_delete_label" .}}
<div class="org-setting-content">
<div class="tw-flex tw-items-center">
<div class="tw-flex-1">
{{ctx.Locale.Tr "org.settings.labels_desc"}}
</div>
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
</div>
<div class="divider"></div>
{{template "repo/issue/labels/label_list" .}}
{{template "repo/issue/labels/label_edit_modal" .}}
</div>
{{template "org/settings/layout_footer" .}}

10 changes: 3 additions & 7 deletions templates/repo/issue/labels.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@
<button class="ui small primary new-label button">{{ctx.Locale.Tr "repo.issues.new_label"}}</button>
{{end}}
</div>
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
{{template "repo/issue/labels/label_new" .}}
{{end}}
{{template "base/alert" .}}
{{template "repo/issue/labels/label_list" .}}
</div>
{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
{{template "repo/issue/labels/label_edit_modal" .}}
{{end}}
</div>

{{if and (or .CanWriteIssues .CanWritePulls) (not .Repository.IsArchived)}}
{{template "repo/issue/labels/edit_delete_label" .}}
{{end}}
{{template "base/footer" .}}
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
<div class="ui g-modal-confirm delete modal">
<div class="header">
{{svg "octicon-trash"}}
{{ctx.Locale.Tr "repo.issues.label_deletion"}}
</div>
<div class="content">
<p>{{ctx.Locale.Tr "repo.issues.label_deletion_desc"}}</p>
</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="ui small modal" id="issue-label-edit-modal"
data-current-page-link="{{$.Link}}"{{/*will be used to construct "new label" and "edit label" URLs*/}}
data-text-new-label="{{ctx.Locale.Tr "repo.issues.new_label"}}"
data-text-edit-label="{{ctx.Locale.Tr "repo.issues.label_modify"}}"
>
<div class="header"></div>
<div class="content">
<form class="ui edit-label form ignore-dirty" action="{{$.Link}}/edit" method="post">
<form class="ui form ignore-dirty" method="post">
{{.CsrfTokenHtml}}
<input id="label-modal-id" name="id" type="hidden">
<input name="id" type="hidden">
<div class="required field">
<label for="name">{{ctx.Locale.Tr "repo.issues.label_title"}}</label>
<div class="ui small input">
Expand Down
18 changes: 12 additions & 6 deletions templates/repo/issue/labels/label_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
{{end}}

<ul class="issue-label-list">
{{$canEditLabel := and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
{{$canEditLabel = or $canEditLabel $.PageIsOrgSettingsLabels}}
{{range .Labels}}
<li class="item">
<div class="label-title">
Expand All @@ -43,12 +45,16 @@
<div class="label-operation tw-flex">
{{template "repo/issue/labels/label_archived" .}}
<div class="tw-flex tw-ml-auto">
{{if and (not $.PageIsOrgSettingsLabels) (not $.Repository.IsArchived) (or $.CanWriteIssues $.CanWritePulls)}}
<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="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
{{else if $.PageIsOrgSettingsLabels}}
<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="delete-button" href="#" data-url="{{$.Link}}/delete" data-id="{{.ID}}">{{svg "octicon-trash"}} {{ctx.Locale.Tr "repo.issues.label_delete"}}</a>
{{if $canEditLabel}}
<a class="edit-label-button" href="#"
data-label-id="{{.ID}}" data-label-name="{{.Name}}" data-label-color="{{.Color}}"
data-label-exclusive="{{.Exclusive}}" data-label-is-archived="{{gt .ArchivedUnix 0}}"
data-label-num-issues="{{.NumIssues}}" data-label-description="{{.Description}}"
>{{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}}
</div>
</div>
Expand Down
48 changes: 0 additions & 48 deletions templates/repo/issue/labels/label_new.tmpl

This file was deleted.

11 changes: 0 additions & 11 deletions web_src/css/repo.css
Original file line number Diff line number Diff line change
Expand Up @@ -2061,17 +2061,6 @@ td .commit-summary {
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 {
display: table;
width: 100%;
Expand Down
2 changes: 1 addition & 1 deletion web_src/js/features/common-organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export function initCommonOrganization() {
});

// Labels
initCompLabelEdit('.organization.settings.labels');
initCompLabelEdit('.page-content.organization.settings.labels');
}
137 changes: 61 additions & 76 deletions web_src/js/features/comp/LabelEdit.ts
Original file line number Diff line number Diff line change
@@ -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);
}

function updateExclusiveLabelEdit(form) {
const nameInput = document.querySelector(`${form} .label-name-input`);
const exclusiveField = document.querySelector(`${form} .label-exclusive-input-field`);
const exclusiveCheckbox = document.querySelector(`${form} .label-exclusive-input`);
const exclusiveWarning = document.querySelector(`${form} .label-exclusive-warning`);
export function initCompLabelEdit(pageSelector: string) {
const pageContent = document.querySelector<HTMLElement>(pageSelector);
if (!pageContent) return;

if (isExclusiveScopeName(nameInput.value)) {
exclusiveField?.classList.remove('muted');
exclusiveField?.removeAttribute('aria-disabled');
if (exclusiveCheckbox.checked && exclusiveCheckbox.getAttribute('data-exclusive-warn')) {
exclusiveWarning?.classList.remove('tw-hidden');
} else {
exclusiveWarning?.classList.add('tw-hidden');
}
} else {
exclusiveField?.classList.add('muted');
exclusiveField?.setAttribute('aria-disabled', 'true');
exclusiveWarning?.classList.add('tw-hidden');
}
}
// for guest view, the modal is not available, the "labels" are read-only
const elModal = pageContent.querySelector<HTMLElement>('#issue-label-edit-modal');
if (!elModal) return;

export function initCompLabelEdit(selector) {
if (!$(selector).length) return;
const elLabelId = elModal.querySelector<HTMLInputElement>('input[name="id"]');
const elNameInput = elModal.querySelector<HTMLInputElement>('.label-name-input');
const elExclusiveField = elModal.querySelector('.label-exclusive-input-field');
const elExclusiveInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-input');
const elExclusiveWarning = elModal.querySelector('.label-exclusive-warning');
const elIsArchivedField = elModal.querySelector('.label-is-archived-input-field');
const elIsArchivedInput = elModal.querySelector<HTMLInputElement>('.label-is-archived-input');
const elDescInput = elModal.querySelector<HTMLInputElement>('.label-desc-input');
const elColorInput = elModal.querySelector<HTMLInputElement>('.js-color-picker-input input');

// 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 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 $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');
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

$('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
// if label id exists: "edit label" mode; otherwise: "new label" mode
const isEdit = Boolean(elLabelId.value);

const colorInput = document.querySelector('.edit-label .js-color-picker-input input');
colorInput.value = this.getAttribute('data-color');
colorInput.dispatchEvent(new Event('input', {bubbles: true}));
// 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');

$('.edit-label.modal').modal({
const curPageLink = elModal.getAttribute('data-current-page-link');
form.action = isEdit ? `${curPageLink}/edit` : `${curPageLink}/new`;
toggleElem(elIsArchivedField, isEdit);
syncModalUi();
fomanticQuery(elModal).modal({
onApprove() {
const form = document.querySelector('.edit-label.form');
if (!form.checkValidity()) {
form.reportValidity();
return false;
}
$('.edit-label.form').trigger('submit');
form.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');
});
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);
});
}
}
2 changes: 1 addition & 1 deletion web_src/js/features/repo-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function initRepository() {
initRepoCommentFormAndSidebar();

// Labels
initCompLabelEdit('.repository.labels');
initCompLabelEdit('.page-content.repository.labels');
initRepoMilestone();
initRepoNew();

Expand Down

0 comments on commit 96d3a03

Please sign in to comment.