0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-04-04 07:55:31 +02:00

reload page partially

This commit is contained in:
wxiaoguang 2026-03-30 22:32:31 +08:00
parent 36810b8900
commit 7762325ebf
6 changed files with 62 additions and 38 deletions

View File

@ -9,7 +9,7 @@
<span class="ui inline required field">
<label>{{ctx.Locale.Tr "actions.workflow.from_ref"}}:</label>
</span>
<div class="ui inline field dropdown button select-branch branch-selector-dropdown ellipsis-text-items">
<div class="ui inline field dropdown button select-branch branch-selector-dropdown ellipsis-text-items" data-global-init="initSelectBranchDropdown">
<input type="hidden" name="ref" hx-sync="this:replace" hx-target="#runWorkflowDispatchModalInputs" hx-swap="innerHTML" hx-get="{{$.Link}}/workflow-dispatch-inputs?workflow={{$.CurWorkflow}}" hx-trigger="change" value="refs/heads/{{index .Branches 0}}">
{{svg "octicon-git-branch" 14}}
<div class="default text">{{index .Branches 0}}</div>

View File

@ -16,6 +16,7 @@ Still needs to figure out:
{{if and (not .Issue.IsPull) (not .PageIsComparePull)}}
<input id="ref_selector" name="ref" type="hidden" value="{{.Reference}}">
<div class="ui dropdown select-branch branch-selector-dropdown ellipsis-text-items {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}}"
data-global-init="initSelectBranchDropdown"
data-no-results="{{ctx.Locale.Tr "no_results_found"}}"
{{if and .Issue (or .IsIssueWriter .HasIssuesOrPullsWritePermission)}}data-url-update-issueref="{{$.RepoLink}}/issues/{{.Issue.Index}}/ref"{{end}}
>

View File

@ -46,7 +46,7 @@
</div>
</div>
<div class="issue-content-right ui segment">
<div class="issue-content-right ui segment" data-global-init="initRepoIssueSidebar">
{{template "repo/issue/branch_selector_field" $}}{{/* TODO: RemoveIssueRef: template "repo/issue/branch_selector_field" $*/}}
{{if .PageIsComparePull}}

View File

@ -1,4 +1,4 @@
<div class="issue-content-right ui segment">
<div class="issue-content-right ui segment" data-global-init="initRepoIssueSidebar">
{{template "repo/issue/branch_selector_field" $}}{{/* TODO: RemoveIssueRef: template "repo/issue/branch_selector_field" $*/}}
{{if .Issue.IsPull}}

View File

@ -1,27 +1,8 @@
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {POST} from '../modules/fetch.ts';
import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {addDelegatedEventListener, queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts';
// if there are draft comments, confirm before reloading, to avoid losing comments
function issueSidebarReloadConfirmDraftComment() {
const commentTextareas = [
document.querySelector<HTMLTextAreaElement>('.edit-content-zone:not(.tw-hidden) textarea'),
document.querySelector<HTMLTextAreaElement>('#comment-form textarea'),
];
for (const textarea of commentTextareas) {
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
if (textarea && textarea.value.trim().length > 10) {
textarea.parentElement!.scrollIntoView();
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
return;
}
break;
}
}
window.location.reload();
}
import {parseDom} from "../utils.ts";
export class IssueSidebarComboList {
updateUrl: string;
@ -33,6 +14,9 @@ export class IssueSidebarComboList {
initialValues: string[];
container: HTMLElement;
elIssueMainContent: HTMLElement;
elIssueSidebar: HTMLElement;
constructor(container: HTMLElement) {
this.container = container;
this.updateUrl = container.getAttribute('data-update-url')!;
@ -43,6 +27,9 @@ export class IssueSidebarComboList {
this.elDropdown = container.querySelector<HTMLElement>(':scope > .ui.dropdown')!;
this.elList = container.querySelector<HTMLElement>(':scope > .ui.list')!;
this.elComboValue = container.querySelector<HTMLInputElement>(':scope > .combo-value')!;
this.elIssueMainContent = document.querySelector('.issue-content-left')!;
this.elIssueSidebar = document.querySelector('.issue-content-right')!;
}
collectCheckedValues() {
@ -63,6 +50,34 @@ export class IssueSidebarComboList {
toggleElem(elEmptyTip, !hasItems);
}
async reloadPagePartially() {
const resp = await GET(window.location.href);
if (!resp.ok) throw new Error(`Failed to reload page: ${resp.statusText}`);
const doc = parseDom(await resp.text(), 'text/html');
// we can safely replace the whole right part (sidebar) because there are only some dropdowns and lists
const newSidebar = doc.querySelector('.issue-content-right')!;
this.elIssueSidebar.replaceWith(newSidebar);
// for the main content (left side), at the moment we only support adding new timeline items
const newMainContent = doc.querySelector('.issue-content-left')!;
// find the last ".timeline-item[id]" in current main content, and insert new items after it, some cases:
// * a new empty issue: "timeline-item comment first", "timeline-item comment form (optional)"
// * issue with timeline events: "timeline-item comment first", "timeline-item event"+id, "timeline-item comment form (optional)"
const timelineItemsWithId = this.elIssueMainContent.querySelectorAll('.timeline-item[id]');
let lastTimelineItemForInsertion = timelineItemsWithId[timelineItemsWithId.length - 1] ?? this.elIssueMainContent.querySelector('.timeline-item');
if (!lastTimelineItemForInsertion) return;
const newTimelineItems = newMainContent.querySelectorAll(`.timeline-item[id]`);
for (const newItem of newTimelineItems) {
const newItemId = newItem.getAttribute('id')!;
if (this.elIssueMainContent.querySelector(`.timeline-item[id="${CSS.escape(newItemId)}"]`)) continue;
lastTimelineItemForInsertion.insertAdjacentElement('afterend', newItem);
lastTimelineItemForInsertion = newItem;
}
}
async sendRequestToBackend(changedValues: Array<string>): Promise<Response> {
if (!changedValues.length) throw new Error('No changed values to update');
let lastResp: Response | null = null;
@ -85,15 +100,18 @@ export class IssueSidebarComboList {
async updateToBackend(changedValues: Array<string>) {
if (!changedValues.length) return;
this.elIssueSidebar.classList.add('is-loading');
try {
const resp = await this.sendRequestToBackend(changedValues);
if (!resp.ok) {
showErrorToast(`Failed to update to backend: ${resp.statusText}`);
return;
}
issueSidebarReloadConfirmDraftComment();
await this.reloadPagePartially();
} catch (e) {
showErrorToast(`Failed to update to backend: ${e}`);
} finally {
this.elIssueSidebar.classList.remove('is-loading');
}
}

View File

@ -1,18 +1,20 @@
import {POST} from '../modules/fetch.ts';
import {queryElems, toggleElem} from '../utils/dom.ts';
import {IssueSidebarComboList} from './repo-issue-sidebar-combolist.ts';
import {registerGlobalInitFunc} from '../modules/observer.ts';
function initBranchSelector() {
// TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch.branch-selector-dropdown');
if (!elSelectBranch) return;
function initBranchSelector(elSelectBranch: HTMLElement) {
// At the moment, 2 places:
// * Issue sidebar branch selector, TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
// * Workflow dispatch branch selector
const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
const elBranchMenu = elSelectBranch.querySelector('.reference-list-menu')!;
queryElems(elBranchMenu, '.item:not(.no-select)', (el) => el.addEventListener('click', async function (e) {
e.preventDefault();
const selectedValue = this.getAttribute('data-id')!; // eg: "refs/heads/my-branch"
const selectedText = this.getAttribute('data-name'); // eg: "my-branch"
// the issue sidebar sets "urlUpdateIssueRef" for the dropdown
if (urlUpdateIssueRef) {
// for existing issue, send request to update issue ref, and reload page
try {
@ -30,23 +32,26 @@ function initBranchSelector() {
}));
}
function initRepoIssueDue() {
const form = document.querySelector<HTMLFormElement>('.issue-due-form');
function initRepoIssueDue(elSidebar: HTMLElement) {
const form = elSidebar.querySelector<HTMLFormElement>('.issue-due-form');
if (!form) return;
const deadline = form.querySelector<HTMLInputElement>('input[name=deadline]')!;
document.querySelector('.issue-due-edit')?.addEventListener('click', () => {
elSidebar.querySelector('.issue-due-edit')?.addEventListener('click', () => {
toggleElem(form);
});
document.querySelector('.issue-due-remove')?.addEventListener('click', () => {
elSidebar.querySelector('.issue-due-remove')?.addEventListener('click', () => {
deadline.value = '';
form.dispatchEvent(new Event('submit', {cancelable: true, bubbles: true}));
});
}
export function initRepoIssueSidebar() {
initBranchSelector();
initRepoIssueDue();
// FIXME: legacy dirty design, the dropdown is reused on Actions page
registerGlobalInitFunc('initSelectBranchDropdown', initBranchSelector);
// init the combo list: a dropdown for selecting items, and a list for showing selected items and related actions
queryElems<HTMLElement>(document, '.issue-sidebar-combo', (el) => new IssueSidebarComboList(el).init());
registerGlobalInitFunc('initRepoIssueSidebar', (elSidebar) => {
initRepoIssueDue(elSidebar);
// init the combo list: a dropdown for selecting items, and a list for showing selected items and related actions
queryElems(elSidebar, '.issue-sidebar-combo', (el) => new IssueSidebarComboList(el).init());
});
}