mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-06 14:36:43 +02:00
Merge branch 'main' into add-file-tree-to-file-view-page
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {showTemporaryTooltip} from '../modules/tippy.ts';
|
||||
import {toAbsoluteUrl} from '../utils.ts';
|
||||
import {clippie} from 'clippie';
|
||||
import type {DOMEvent} from '../utils/dom.ts';
|
||||
|
||||
const {copy_success, copy_error} = window.config.i18n;
|
||||
|
||||
@@ -9,7 +10,7 @@ const {copy_success, copy_error} = window.config.i18n;
|
||||
// - data-clipboard-target: Holds a selector for a <input> or <textarea> whose content is copied
|
||||
// - data-clipboard-text-type: When set to 'url' will convert relative to absolute urls
|
||||
export function initGlobalCopyToClipboardListener() {
|
||||
document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
||||
document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
|
||||
const target = e.target.closest('[data-clipboard-text], [data-clipboard-target]');
|
||||
if (!target) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {createTippy} from '../modules/tippy.ts';
|
||||
import type {DOMEvent} from '../utils/dom.ts';
|
||||
|
||||
export async function initColorPickers() {
|
||||
const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input');
|
||||
@@ -37,7 +38,7 @@ function initPicker(el: HTMLElement): void {
|
||||
updateSquare(square, e.detail.value);
|
||||
});
|
||||
|
||||
input.addEventListener('input', (e: Event & {target: HTMLInputElement}) => {
|
||||
input.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => {
|
||||
updateSquare(square, e.target.value);
|
||||
updatePicker(picker, e.target.value);
|
||||
});
|
||||
@@ -55,8 +56,8 @@ function initPicker(el: HTMLElement): void {
|
||||
});
|
||||
|
||||
// init precolors
|
||||
for (const colorEl of el.querySelectorAll('.precolors .color')) {
|
||||
colorEl.addEventListener('click', (e: MouseEvent & {target: HTMLAnchorElement}) => {
|
||||
for (const colorEl of el.querySelectorAll<HTMLElement>('.precolors .color')) {
|
||||
colorEl.addEventListener('click', (e: DOMEvent<MouseEvent, HTMLAnchorElement>) => {
|
||||
const newValue = e.target.getAttribute('data-color-hex');
|
||||
input.value = newValue;
|
||||
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
|
||||
@@ -3,6 +3,7 @@ import {showErrorToast} from '../modules/toast.ts';
|
||||
import {addDelegatedEventListener, submitEventSubmitter} from '../utils/dom.ts';
|
||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||
import type {RequestOpts} from '../types.ts';
|
||||
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
|
||||
const {appSubUrl, i18n} = window.config;
|
||||
|
||||
@@ -27,7 +28,7 @@ async function fetchActionDoRequest(actionElem: HTMLElement, url: string, opt: R
|
||||
if (resp.status === 200) {
|
||||
let {redirect} = await resp.json();
|
||||
redirect = redirect || actionElem.getAttribute('data-redirect');
|
||||
actionElem.classList.remove('dirty'); // remove the areYouSure check before reloading
|
||||
ignoreAreYouSure(actionElem); // ignore the areYouSure check before reloading
|
||||
if (redirect) {
|
||||
fetchActionDoRedirect(redirect);
|
||||
} else {
|
||||
@@ -100,7 +101,7 @@ async function linkAction(el: HTMLElement, e: Event) {
|
||||
const url = el.getAttribute('data-url');
|
||||
const doRequest = async () => {
|
||||
if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but A doesn't have disabled attribute
|
||||
await fetchActionDoRequest(el, url, {method: 'POST'});
|
||||
await fetchActionDoRequest(el, url, {method: el.getAttribute('data-link-action-method') || 'POST'});
|
||||
if ('disabled' in el) el.disabled = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts';
|
||||
import {queryElems} from '../utils/dom.ts';
|
||||
import {queryElems, type DOMEvent} from '../utils/dom.ts';
|
||||
import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||
|
||||
export function initGlobalFormDirtyLeaveConfirm() {
|
||||
@@ -13,7 +13,7 @@ export function initGlobalFormDirtyLeaveConfirm() {
|
||||
}
|
||||
|
||||
export function initGlobalEnterQuickSubmit() {
|
||||
document.addEventListener('keydown', (e: KeyboardEvent & {target: HTMLElement}) => {
|
||||
document.addEventListener('keydown', (e: DOMEvent<KeyboardEvent>) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
|
||||
if (hasCtrlOrMeta && e.target.matches('textarea')) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {showElem} from '../../utils/dom.ts';
|
||||
import {showElem, type DOMEvent} from '../../utils/dom.ts';
|
||||
|
||||
type CropperOpts = {
|
||||
container: HTMLElement,
|
||||
@@ -26,7 +26,7 @@ export async function initCompCropper({container, fileInput, imageSource}: Cropp
|
||||
},
|
||||
});
|
||||
|
||||
fileInput.addEventListener('input', (e: Event & {target: HTMLInputElement}) => {
|
||||
fileInput.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
if (files?.length > 0) {
|
||||
currentFileName = files[0].name;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {POST} from '../../modules/fetch.ts';
|
||||
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
||||
import type {DOMEvent} from '../../utils/dom.ts';
|
||||
|
||||
export function initCompReactionSelector(parent: ParentNode = document) {
|
||||
for (const container of parent.querySelectorAll('.issue-content, .diff-file-body')) {
|
||||
container.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
||||
for (const container of parent.querySelectorAll<HTMLElement>('.issue-content, .diff-file-body')) {
|
||||
container.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
|
||||
// there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment
|
||||
const target = e.target.closest('.comment-reaction-button');
|
||||
if (!target) return;
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
const sourcesByUrl = {};
|
||||
const sourcesByPort = {};
|
||||
|
||||
class Source {
|
||||
url: string;
|
||||
eventSource: EventSource;
|
||||
listening: Record<string, any>;
|
||||
clients: Array<any>;
|
||||
listening: Record<string, boolean>;
|
||||
clients: Array<MessagePort>;
|
||||
|
||||
constructor(url) {
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
this.eventSource = new EventSource(url);
|
||||
this.listening = {};
|
||||
@@ -20,7 +17,7 @@ class Source {
|
||||
this.listen('error');
|
||||
}
|
||||
|
||||
register(port) {
|
||||
register(port: MessagePort) {
|
||||
if (this.clients.includes(port)) return;
|
||||
|
||||
this.clients.push(port);
|
||||
@@ -31,7 +28,7 @@ class Source {
|
||||
});
|
||||
}
|
||||
|
||||
deregister(port) {
|
||||
deregister(port: MessagePort) {
|
||||
const portIdx = this.clients.indexOf(port);
|
||||
if (portIdx < 0) {
|
||||
return this.clients.length;
|
||||
@@ -47,7 +44,7 @@ class Source {
|
||||
this.eventSource = null;
|
||||
}
|
||||
|
||||
listen(eventType) {
|
||||
listen(eventType: string) {
|
||||
if (this.listening[eventType]) return;
|
||||
this.listening[eventType] = true;
|
||||
this.eventSource.addEventListener(eventType, (event) => {
|
||||
@@ -58,13 +55,13 @@ class Source {
|
||||
});
|
||||
}
|
||||
|
||||
notifyClients(event) {
|
||||
notifyClients(event: {type: string, data: any}) {
|
||||
for (const client of this.clients) {
|
||||
client.postMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
status(port) {
|
||||
status(port: MessagePort) {
|
||||
port.postMessage({
|
||||
type: 'status',
|
||||
message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,
|
||||
@@ -72,7 +69,11 @@ class Source {
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener('connect', (e: Event & {ports: Array<any>}) => {
|
||||
const sourcesByUrl: Map<string, Source | null> = new Map();
|
||||
const sourcesByPort: Map<MessagePort, Source | null> = new Map();
|
||||
|
||||
// @ts-expect-error: typescript bug?
|
||||
self.addEventListener('connect', (e: MessageEvent) => {
|
||||
for (const port of e.ports) {
|
||||
port.addEventListener('message', (event) => {
|
||||
if (!self.EventSource) {
|
||||
@@ -84,14 +85,14 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => {
|
||||
}
|
||||
if (event.data.type === 'start') {
|
||||
const url = event.data.url;
|
||||
if (sourcesByUrl[url]) {
|
||||
if (sourcesByUrl.get(url)) {
|
||||
// we have a Source registered to this url
|
||||
const source = sourcesByUrl[url];
|
||||
const source = sourcesByUrl.get(url);
|
||||
source.register(port);
|
||||
sourcesByPort[port] = source;
|
||||
sourcesByPort.set(port, source);
|
||||
return;
|
||||
}
|
||||
let source = sourcesByPort[port];
|
||||
let source = sourcesByPort.get(port);
|
||||
if (source) {
|
||||
if (source.eventSource && source.url === url) return;
|
||||
|
||||
@@ -101,30 +102,30 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => {
|
||||
// Clean-up
|
||||
if (count === 0) {
|
||||
source.close();
|
||||
sourcesByUrl[source.url] = null;
|
||||
sourcesByUrl.set(source.url, null);
|
||||
}
|
||||
}
|
||||
// Create a new Source
|
||||
source = new Source(url);
|
||||
source.register(port);
|
||||
sourcesByUrl[url] = source;
|
||||
sourcesByPort[port] = source;
|
||||
sourcesByUrl.set(url, source);
|
||||
sourcesByPort.set(port, source);
|
||||
} else if (event.data.type === 'listen') {
|
||||
const source = sourcesByPort[port];
|
||||
const source = sourcesByPort.get(port);
|
||||
source.listen(event.data.eventType);
|
||||
} else if (event.data.type === 'close') {
|
||||
const source = sourcesByPort[port];
|
||||
const source = sourcesByPort.get(port);
|
||||
|
||||
if (!source) return;
|
||||
|
||||
const count = source.deregister(port);
|
||||
if (count === 0) {
|
||||
source.close();
|
||||
sourcesByUrl[source.url] = null;
|
||||
sourcesByPort[port] = null;
|
||||
sourcesByUrl.set(source.url, null);
|
||||
sourcesByPort.set(port, null);
|
||||
}
|
||||
} else if (event.data.type === 'status') {
|
||||
const source = sourcesByPort[port];
|
||||
const source = sourcesByPort.get(port);
|
||||
if (!source) {
|
||||
port.postMessage({
|
||||
type: 'status',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import $ from 'jquery';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {toggleElem} from '../utils/dom.ts';
|
||||
import {toggleElem, type DOMEvent} from '../utils/dom.ts';
|
||||
import {logoutFromWorker} from '../modules/worker.ts';
|
||||
|
||||
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
|
||||
@@ -25,8 +25,8 @@ export function initNotificationsTable() {
|
||||
});
|
||||
|
||||
// mark clicked unread links for deletion on bfcache restore
|
||||
for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) {
|
||||
link.addEventListener('click', (e : MouseEvent & {target: HTMLElement}) => {
|
||||
for (const link of table.querySelectorAll<HTMLAnchorElement>('.notifications-item[data-status="1"] .notifications-link')) {
|
||||
link.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
||||
e.target.closest('.notifications-item').setAttribute('data-remove', 'true');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type {DOMEvent} from '../utils/dom.ts';
|
||||
|
||||
export function initOAuth2SettingsDisableCheckbox() {
|
||||
for (const el of document.querySelectorAll('.disable-setting')) {
|
||||
el.addEventListener('change', (e: Event & {target: HTMLInputElement}) => {
|
||||
for (const el of document.querySelectorAll<HTMLInputElement>('.disable-setting')) {
|
||||
el.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => {
|
||||
document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {attachRefIssueContextPopup} from './contextpopup.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {initDropzone} from './dropzone.ts';
|
||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||
import {applyAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {applyAreYouSure, ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
|
||||
function initEditPreviewTab(elForm: HTMLFormElement) {
|
||||
@@ -188,7 +188,7 @@ export function initRepoEditor() {
|
||||
header: elForm.getAttribute('data-text-empty-confirm-header'),
|
||||
content: elForm.getAttribute('data-text-empty-confirm-content'),
|
||||
})) {
|
||||
elForm.classList.remove('dirty');
|
||||
ignoreAreYouSure(elForm);
|
||||
elForm.submit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {hideElem, showElem} from '../utils/dom.ts';
|
||||
import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
|
||||
export function initRepoGraphGit() {
|
||||
const graphContainer = document.querySelector('#git-graph-container');
|
||||
const graphContainer = document.querySelector<HTMLElement>('#git-graph-container');
|
||||
if (!graphContainer) return;
|
||||
|
||||
document.querySelector('#flow-color-monochrome')?.addEventListener('click', () => {
|
||||
@@ -87,7 +87,7 @@ export function initRepoGraphGit() {
|
||||
fomanticQuery(flowSelectRefsDropdown).dropdown({
|
||||
clearable: true,
|
||||
fullTextSeach: 'exact',
|
||||
onRemove(toRemove) {
|
||||
onRemove(toRemove: string) {
|
||||
if (toRemove === '...flow-hide-pr-refs') {
|
||||
params.delete('hide-pr-refs');
|
||||
} else {
|
||||
@@ -101,7 +101,7 @@ export function initRepoGraphGit() {
|
||||
}
|
||||
updateGraph();
|
||||
},
|
||||
onAdd(toAdd) {
|
||||
onAdd(toAdd: string) {
|
||||
if (toAdd === '...flow-hide-pr-refs') {
|
||||
params.set('hide-pr-refs', 'true');
|
||||
} else {
|
||||
@@ -111,7 +111,7 @@ export function initRepoGraphGit() {
|
||||
},
|
||||
});
|
||||
|
||||
graphContainer.addEventListener('mouseenter', (e: MouseEvent & {target: HTMLElement}) => {
|
||||
graphContainer.addEventListener('mouseenter', (e: DOMEvent<MouseEvent>) => {
|
||||
if (e.target.matches('#rev-list li')) {
|
||||
const flow = e.target.getAttribute('data-flow');
|
||||
if (flow === '0') return;
|
||||
@@ -132,7 +132,7 @@ export function initRepoGraphGit() {
|
||||
}
|
||||
});
|
||||
|
||||
graphContainer.addEventListener('mouseleave', (e: MouseEvent & {target: HTMLElement}) => {
|
||||
graphContainer.addEventListener('mouseleave', (e: DOMEvent<MouseEvent>) => {
|
||||
if (e.target.matches('#rev-list li')) {
|
||||
const flow = e.target.getAttribute('data-flow');
|
||||
if (flow === '0') return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {stripTags} from '../utils.ts';
|
||||
import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts';
|
||||
import {hideElem, queryElemChildren, showElem, type DOMEvent} from '../utils/dom.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast, type Toast} from '../modules/toast.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
@@ -28,7 +28,7 @@ export function initRepoTopicBar() {
|
||||
mgrBtn.focus();
|
||||
});
|
||||
|
||||
document.querySelector('#save_topic').addEventListener('click', async (e: MouseEvent & {target: HTMLButtonElement}) => {
|
||||
document.querySelector<HTMLButtonElement>('#save_topic').addEventListener('click', async (e: DOMEvent<MouseEvent, HTMLButtonElement>) => {
|
||||
lastErrorToast?.hideToast();
|
||||
const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {attachRefIssueContextPopup} from './contextpopup.ts';
|
||||
import {initCommentContent, initMarkupContent} from '../markup/content.ts';
|
||||
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
|
||||
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
|
||||
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
|
||||
async function tryOnEditContent(e) {
|
||||
const clickTarget = e.target.closest('.edit-content');
|
||||
@@ -48,6 +49,7 @@ async function tryOnEditContent(e) {
|
||||
showErrorToast(data.errorMessage);
|
||||
return;
|
||||
}
|
||||
reinitializeAreYouSure(editContentZone.querySelector('form')); // the form is no longer dirty
|
||||
editContentZone.setAttribute('data-content-version', data.contentVersion);
|
||||
if (!data.content) {
|
||||
renderContent.innerHTML = document.querySelector('#no-content').innerHTML;
|
||||
@@ -86,13 +88,15 @@ async function tryOnEditContent(e) {
|
||||
comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
|
||||
if (!comboMarkdownEditor) {
|
||||
editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML;
|
||||
const form = editContentZone.querySelector('form');
|
||||
applyAreYouSure(form);
|
||||
const saveButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.primary.button');
|
||||
const cancelButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.cancel.button');
|
||||
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
|
||||
const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading();
|
||||
comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
|
||||
cancelButton.addEventListener('click', cancelAndReset);
|
||||
saveButton.addEventListener('click', saveAndRefresh);
|
||||
form.addEventListener('submit', saveAndRefresh);
|
||||
}
|
||||
|
||||
// FIXME: ideally here should reload content and attachment list from backend for existing editor, to avoid losing data
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
queryElems,
|
||||
showElem,
|
||||
toggleElem,
|
||||
type DOMEvent,
|
||||
} from '../utils/dom.ts';
|
||||
import {setFileFolding} from './file-fold.ts';
|
||||
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||
@@ -16,6 +17,7 @@ import {GET, POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
@@ -52,7 +54,7 @@ export function initRepoIssueSidebarList() {
|
||||
});
|
||||
}
|
||||
|
||||
function initRepoIssueLabelFilter(elDropdown: Element) {
|
||||
function initRepoIssueLabelFilter(elDropdown: HTMLElement) {
|
||||
const url = new URL(window.location.href);
|
||||
const showArchivedLabels = url.searchParams.get('archived_labels') === 'true';
|
||||
const queryLabels = url.searchParams.get('labels') || '';
|
||||
@@ -124,7 +126,7 @@ export function initRepoIssueFilterItemLabel() {
|
||||
|
||||
export function initRepoIssueCommentDelete() {
|
||||
// Delete comment
|
||||
document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
||||
document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
|
||||
if (!e.target.matches('.delete-comment')) return;
|
||||
e.preventDefault();
|
||||
|
||||
@@ -199,7 +201,7 @@ export function initRepoIssueDependencyDelete() {
|
||||
|
||||
export function initRepoIssueCodeCommentCancel() {
|
||||
// Cancel inline code comment
|
||||
document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
|
||||
document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
||||
if (!e.target.matches('.cancel-code-comment')) return;
|
||||
|
||||
const form = e.target.closest('form');
|
||||
@@ -221,7 +223,7 @@ export function initRepoPullRequestUpdate() {
|
||||
e.preventDefault();
|
||||
const redirect = this.getAttribute('data-redirect');
|
||||
this.classList.add('is-loading');
|
||||
let response;
|
||||
let response: Response;
|
||||
try {
|
||||
response = await POST(this.getAttribute('data-do'));
|
||||
} catch (error) {
|
||||
@@ -229,7 +231,7 @@ export function initRepoPullRequestUpdate() {
|
||||
} finally {
|
||||
this.classList.remove('is-loading');
|
||||
}
|
||||
let data;
|
||||
let data: Record<string, any>;
|
||||
try {
|
||||
data = await response?.json(); // the response is probably not a JSON
|
||||
} catch (error) {
|
||||
@@ -340,7 +342,7 @@ export function initRepoIssueWipTitle() {
|
||||
export function initRepoIssueComments() {
|
||||
if (!$('.repository.view.issue .timeline').length) return;
|
||||
|
||||
document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
|
||||
document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
||||
const urlTarget = document.querySelector(':target');
|
||||
if (!urlTarget) return;
|
||||
|
||||
@@ -532,7 +534,7 @@ export function initRepoIssueWipToggle() {
|
||||
|
||||
export function initRepoIssueTitleEdit() {
|
||||
const issueTitleDisplay = document.querySelector('#issue-title-display');
|
||||
const issueTitleEditor = document.querySelector('#issue-title-editor');
|
||||
const issueTitleEditor = document.querySelector<HTMLFormElement>('#issue-title-editor');
|
||||
if (!issueTitleEditor) return;
|
||||
|
||||
const issueTitleInput = issueTitleEditor.querySelector('input');
|
||||
@@ -558,7 +560,8 @@ export function initRepoIssueTitleEdit() {
|
||||
const prTargetUpdateUrl = pullDescEditor?.getAttribute('data-target-update-url');
|
||||
|
||||
const editSaveButton = issueTitleEditor.querySelector('.ui.primary.button');
|
||||
editSaveButton.addEventListener('click', async () => {
|
||||
issueTitleEditor.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const newTitle = issueTitleInput.value.trim();
|
||||
try {
|
||||
if (newTitle && newTitle !== oldTitle) {
|
||||
@@ -577,6 +580,7 @@ export function initRepoIssueTitleEdit() {
|
||||
}
|
||||
}
|
||||
}
|
||||
ignoreAreYouSure(issueTitleEditor);
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -586,7 +590,7 @@ export function initRepoIssueTitleEdit() {
|
||||
}
|
||||
|
||||
export function initRepoIssueBranchSelect() {
|
||||
document.querySelector('#branch-select')?.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
|
||||
document.querySelector<HTMLElement>('#branch-select')?.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
||||
const el = e.target.closest('.item[data-branch]');
|
||||
if (!el) return;
|
||||
const pullTargetBranch = document.querySelector('#pull-target-branch');
|
||||
@@ -625,10 +629,10 @@ function initIssueTemplateCommentEditors($commentForm) {
|
||||
// * new issue with issue template
|
||||
const $comboFields = $commentForm.find('.combo-editor-dropzone');
|
||||
|
||||
const initCombo = async (elCombo) => {
|
||||
const initCombo = async (elCombo: HTMLElement) => {
|
||||
const $formField = $(elCombo.querySelector('.form-field-real'));
|
||||
const dropzoneContainer = elCombo.querySelector('.form-field-dropzone');
|
||||
const markdownEditor = elCombo.querySelector('.combo-markdown-editor');
|
||||
const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone');
|
||||
const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor');
|
||||
|
||||
const editor = await initComboMarkdownEditor(markdownEditor);
|
||||
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value()));
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
initRepoPullRequestUpdate,
|
||||
} from './repo-issue.ts';
|
||||
import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
|
||||
import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
|
||||
import {initRepoCloneButtons} from './repo-common.ts';
|
||||
import {initCitationFileCopyContent} from './citation.ts';
|
||||
import {initCompLabelEdit} from './comp/LabelEdit.ts';
|
||||
@@ -20,6 +19,14 @@ import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts';
|
||||
import {initRepoIssueCommentEdit} from './repo-issue-edit.ts';
|
||||
import {initRepoMilestone} from './repo-milestone.ts';
|
||||
import {initRepoNew} from './repo-new.ts';
|
||||
import {createApp} from 'vue';
|
||||
import RepoBranchTagSelector from '../components/RepoBranchTagSelector.vue';
|
||||
|
||||
function initRepoBranchTagSelector(selector: string) {
|
||||
for (const elRoot of document.querySelectorAll(selector)) {
|
||||
createApp(RepoBranchTagSelector, {elRoot}).mount(elRoot);
|
||||
}
|
||||
}
|
||||
|
||||
export function initBranchSelectorTabs() {
|
||||
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
import $ from 'jquery';
|
||||
import {contrastColor} from '../utils/color.ts';
|
||||
import {createSortable} from '../modules/sortable.ts';
|
||||
import {POST, DELETE, PUT} from '../modules/fetch.ts';
|
||||
import {POST, request} from '../modules/fetch.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {queryElemChildren, queryElems} from '../utils/dom.ts';
|
||||
import type {SortableEvent} from 'sortablejs';
|
||||
|
||||
function updateIssueCount(cards) {
|
||||
const parent = cards.parentElement;
|
||||
const cnt = parent.querySelectorAll('.issue-card').length;
|
||||
parent.querySelectorAll('.project-column-issue-count')[0].textContent = cnt;
|
||||
function updateIssueCount(card: HTMLElement): void {
|
||||
const parent = card.parentElement;
|
||||
const count = parent.querySelectorAll('.issue-card').length;
|
||||
parent.querySelector('.project-column-issue-count').textContent = String(count);
|
||||
}
|
||||
|
||||
async function createNewColumn(url, columnTitle, projectColorInput) {
|
||||
try {
|
||||
await POST(url, {
|
||||
data: {
|
||||
title: columnTitle.val(),
|
||||
color: projectColorInput.val(),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
columnTitle.closest('form').removeClass('dirty');
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function moveIssue({item, from, to, oldIndex}: {item: HTMLElement, from: HTMLElement, to: HTMLElement, oldIndex: number}) {
|
||||
async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<void> {
|
||||
const columnCards = to.querySelectorAll('.issue-card');
|
||||
updateIssueCount(from);
|
||||
updateIssueCount(to);
|
||||
@@ -47,13 +33,10 @@ async function moveIssue({item, from, to, oldIndex}: {item: HTMLElement, from: H
|
||||
}
|
||||
}
|
||||
|
||||
async function initRepoProjectSortable() {
|
||||
const els = document.querySelectorAll('#project-board > .board.sortable');
|
||||
if (!els.length) return;
|
||||
|
||||
async function initRepoProjectSortable(): Promise<void> {
|
||||
// the HTML layout is: #project-board > .board > .project-column .cards > .issue-card
|
||||
const mainBoard = els[0];
|
||||
let boardColumns = mainBoard.querySelectorAll('.project-column');
|
||||
const mainBoard = document.querySelector('#project-board > .board.sortable');
|
||||
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
||||
createSortable(mainBoard, {
|
||||
group: 'project-column',
|
||||
draggable: '.project-column',
|
||||
@@ -61,7 +44,7 @@ async function initRepoProjectSortable() {
|
||||
delayOnTouchOnly: true,
|
||||
delay: 500,
|
||||
onSort: async () => { // eslint-disable-line @typescript-eslint/no-misused-promises
|
||||
boardColumns = mainBoard.querySelectorAll('.project-column');
|
||||
boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
||||
|
||||
const columnSorting = {
|
||||
columns: Array.from(boardColumns, (column, i) => ({
|
||||
@@ -81,7 +64,7 @@ async function initRepoProjectSortable() {
|
||||
});
|
||||
|
||||
for (const boardColumn of boardColumns) {
|
||||
const boardCardList = boardColumn.querySelectorAll('.cards')[0];
|
||||
const boardCardList = boardColumn.querySelector('.cards');
|
||||
createSortable(boardCardList, {
|
||||
group: 'shared',
|
||||
onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
|
||||
@@ -92,97 +75,74 @@ async function initRepoProjectSortable() {
|
||||
}
|
||||
}
|
||||
|
||||
export function initRepoProject() {
|
||||
if (!document.querySelector('.repository.projects')) {
|
||||
return;
|
||||
}
|
||||
function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
|
||||
const elModal = document.querySelector<HTMLElement>('.ui.modal#project-column-modal-edit');
|
||||
const elForm = elModal.querySelector<HTMLFormElement>('form');
|
||||
|
||||
initRepoProjectSortable(); // no await
|
||||
const elColumnId = elForm.querySelector<HTMLInputElement>('input[name="id"]');
|
||||
const elColumnTitle = elForm.querySelector<HTMLInputElement>('input[name="title"]');
|
||||
const elColumnColor = elForm.querySelector<HTMLInputElement>('input[name="color"]');
|
||||
|
||||
for (const modal of document.querySelectorAll('.edit-project-column-modal')) {
|
||||
const projectHeader = modal.closest<HTMLElement>('.project-column-header');
|
||||
const projectTitleLabel = projectHeader?.querySelector<HTMLElement>('.project-column-title-label');
|
||||
const projectTitleInput = modal.querySelector<HTMLInputElement>('.project-column-title-input');
|
||||
const projectColorInput = modal.querySelector<HTMLInputElement>('#new_project_column_color');
|
||||
const boardColumn = modal.closest<HTMLElement>('.project-column');
|
||||
modal.querySelector('.edit-project-column-button')?.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await PUT(this.getAttribute('data-url'), {
|
||||
data: {
|
||||
title: projectTitleInput?.value,
|
||||
color: projectColorInput?.value,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
projectTitleLabel.textContent = projectTitleInput?.value;
|
||||
projectTitleInput.closest('form')?.classList.remove('dirty');
|
||||
const dividers = boardColumn.querySelectorAll<HTMLElement>(':scope > .divider');
|
||||
if (projectColorInput.value) {
|
||||
const color = contrastColor(projectColorInput.value);
|
||||
boardColumn.style.setProperty('background', projectColorInput.value, 'important');
|
||||
boardColumn.style.setProperty('color', color, 'important');
|
||||
for (const divider of dividers) {
|
||||
divider.style.setProperty('color', color);
|
||||
}
|
||||
} else {
|
||||
boardColumn.style.removeProperty('background');
|
||||
boardColumn.style.removeProperty('color');
|
||||
for (const divider of dividers) {
|
||||
divider.style.removeProperty('color');
|
||||
}
|
||||
}
|
||||
$('.ui.modal').modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
const attrDataColumnId = 'data-modal-project-column-id';
|
||||
const attrDataColumnTitle = 'data-modal-project-column-title-input';
|
||||
const attrDataColumnColor = 'data-modal-project-column-color-input';
|
||||
|
||||
$('.default-project-column-modal').each(function () {
|
||||
const $boardColumn = $(this).closest('.project-column');
|
||||
const $showButton = $($boardColumn).find('.default-project-column-show');
|
||||
const $commitButton = $(this).find('.actions > .ok.button');
|
||||
|
||||
$($commitButton).on('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await POST($($showButton).data('url'));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
window.location.reload();
|
||||
}
|
||||
// the "new" button is not in project board, so need to query from document
|
||||
queryElems(document, '.show-project-column-modal-edit', (el) => {
|
||||
el.addEventListener('click', () => {
|
||||
elColumnId.value = el.getAttribute(attrDataColumnId);
|
||||
elColumnTitle.value = el.getAttribute(attrDataColumnTitle);
|
||||
elColumnColor.value = el.getAttribute(attrDataColumnColor);
|
||||
elColumnColor.dispatchEvent(new Event('input', {bubbles: true})); // trigger the color picker
|
||||
});
|
||||
});
|
||||
|
||||
$('.show-delete-project-column-modal').each(function () {
|
||||
const $deleteColumnModal = $(`${this.getAttribute('data-modal')}`);
|
||||
const $deleteColumnButton = $deleteColumnModal.find('.actions > .ok.button');
|
||||
const deleteUrl = this.getAttribute('data-url');
|
||||
|
||||
$deleteColumnButton.on('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
await DELETE(deleteUrl);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#new_project_column_submit').on('click', (e) => {
|
||||
elForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const $columnTitle = $('#new_project_column');
|
||||
const $projectColorInput = $('#new_project_column_color_picker');
|
||||
if (!$columnTitle.val()) {
|
||||
return;
|
||||
const columnId = elColumnId.value;
|
||||
const actionBaseLink = elForm.getAttribute('data-action-base-link');
|
||||
|
||||
const formData = new FormData(elForm);
|
||||
const formLink = columnId ? `${actionBaseLink}/${columnId}` : `${actionBaseLink}/columns/new`;
|
||||
const formMethod = columnId ? 'PUT' : 'POST';
|
||||
|
||||
try {
|
||||
elForm.classList.add('is-loading');
|
||||
await request(formLink, {method: formMethod, data: formData});
|
||||
if (!columnId) {
|
||||
window.location.reload(); // newly added column, need to reload the page
|
||||
return;
|
||||
}
|
||||
fomanticQuery(elModal).modal('hide');
|
||||
|
||||
// update the newly saved column title and color in the project board (to avoid reload)
|
||||
const elEditButton = writableProjectBoard.querySelector<HTMLButtonElement>(`.show-project-column-modal-edit[${attrDataColumnId}="${columnId}"]`);
|
||||
elEditButton.setAttribute(attrDataColumnTitle, elColumnTitle.value);
|
||||
elEditButton.setAttribute(attrDataColumnColor, elColumnColor.value);
|
||||
|
||||
const elBoardColumn = writableProjectBoard.querySelector<HTMLElement>(`.project-column[data-id="${columnId}"]`);
|
||||
const elBoardColumnTitle = elBoardColumn.querySelector<HTMLElement>(`.project-column-title-text`);
|
||||
elBoardColumnTitle.textContent = elColumnTitle.value;
|
||||
if (elColumnColor.value) {
|
||||
const textColor = contrastColor(elColumnColor.value);
|
||||
elBoardColumn.style.setProperty('background', elColumnColor.value, 'important');
|
||||
elBoardColumn.style.setProperty('color', textColor, 'important');
|
||||
queryElemChildren<HTMLElement>(elBoardColumn, '.divider', (divider) => divider.style.color = textColor);
|
||||
} else {
|
||||
elBoardColumn.style.removeProperty('background');
|
||||
elBoardColumn.style.removeProperty('color');
|
||||
queryElemChildren<HTMLElement>(elBoardColumn, '.divider', (divider) => divider.style.removeProperty('color'));
|
||||
}
|
||||
} finally {
|
||||
elForm.classList.remove('is-loading');
|
||||
}
|
||||
const url = e.target.getAttribute('data-url');
|
||||
createNewColumn(url, $columnTitle, $projectColorInput);
|
||||
});
|
||||
}
|
||||
|
||||
export function initRepoProject(): void {
|
||||
const writableProjectBoard = document.querySelector('#project-board[data-project-borad-writable="true"]');
|
||||
if (!writableProjectBoard) return;
|
||||
|
||||
initRepoProjectSortable(); // no await
|
||||
initRepoProjectColumnEdit(writableProjectBoard);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {hideElem, showElem} from '../utils/dom.ts';
|
||||
import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
||||
|
||||
export function initRepoRelease() {
|
||||
document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
|
||||
document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
||||
if (e.target.matches('.remove-rel-attach')) {
|
||||
const uuid = e.target.getAttribute('data-uuid');
|
||||
const id = e.target.getAttribute('data-id');
|
||||
@@ -42,7 +42,7 @@ function initTagNameEditor() {
|
||||
}
|
||||
};
|
||||
hideTargetInput(tagNameInput); // update on page load because the input may have a value
|
||||
tagNameInput.addEventListener('input', (e: InputEvent & {target: HTMLInputElement}) => {
|
||||
hideTargetInput(e.target);
|
||||
tagNameInput.addEventListener('input', (e) => {
|
||||
hideTargetInput(e.target as HTMLInputElement);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type {DOMEvent} from '../utils/dom.ts';
|
||||
|
||||
export function initRepositorySearch() {
|
||||
const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form');
|
||||
if (!repositorySearchForm) return;
|
||||
|
||||
repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => {
|
||||
repositorySearchForm.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
@@ -2,7 +2,8 @@ import {beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {createSortable} from '../modules/sortable.ts';
|
||||
import type {SortableEvent} from 'sortablejs';
|
||||
import type {SortableEvent, SortableOptions} from 'sortablejs';
|
||||
import type Sortable from 'sortablejs';
|
||||
|
||||
vi.mock('../modules/fetch.ts', () => ({
|
||||
POST: vi.fn(),
|
||||
@@ -55,9 +56,10 @@ describe('Repository Branch Settings', () => {
|
||||
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
||||
|
||||
// Mock createSortable to capture and execute the onEnd callback
|
||||
vi.mocked(createSortable).mockImplementation(async (_el: Element, options) => {
|
||||
vi.mocked(createSortable).mockImplementation(async (_el: Element, options: SortableOptions) => {
|
||||
options.onEnd(new Event('SortableEvent') as SortableEvent);
|
||||
return {destroy: vi.fn()};
|
||||
// @ts-expect-error: mock is incomplete
|
||||
return {destroy: vi.fn()} as Sortable;
|
||||
});
|
||||
|
||||
initRepoSettingsBranchesDrag();
|
||||
|
||||
@@ -122,7 +122,7 @@ function initRepoSettingsBranches() {
|
||||
function initRepoSettingsOptions() {
|
||||
if ($('.repository.settings.options').length > 0) {
|
||||
// Enable or select internal/external wiki system and issue tracker.
|
||||
$('.enable-system').on('change', function (this: HTMLInputElement) {
|
||||
$('.enable-system').on('change', function (this: HTMLInputElement) { // eslint-disable-line @typescript-eslint/no-deprecated
|
||||
if (this.checked) {
|
||||
$($(this).data('target')).removeClass('disabled');
|
||||
if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
|
||||
@@ -131,7 +131,7 @@ function initRepoSettingsOptions() {
|
||||
if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
|
||||
}
|
||||
});
|
||||
$('.enable-system-radio').on('change', function (this: HTMLInputElement) {
|
||||
$('.enable-system-radio').on('change', function (this: HTMLInputElement) { // eslint-disable-line @typescript-eslint/no-deprecated
|
||||
if (this.value === 'false') {
|
||||
$($(this).data('target')).addClass('disabled');
|
||||
if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');
|
||||
|
||||
@@ -13,7 +13,7 @@ async function initRepoWikiFormEditor() {
|
||||
let editor: ComboMarkdownEditor;
|
||||
|
||||
let renderRequesting = false;
|
||||
let lastContent;
|
||||
let lastContent: string = '';
|
||||
const renderEasyMDEPreview = async function () {
|
||||
if (renderRequesting) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user