mirror of https://github.com/go-gitea/gitea.git
Enable Typescript `strictFunctionTypes` (#32911)
1. Enable [strictFunctionTypes](https://www.typescriptlang.org/tsconfig/#strictFunctionTypes) 2. Introduce `DOMEvent` helper type which sets `e.target`. Surely not totally correct with that `Partial` but seems to work. 3. Various type-related refactors, change objects in `eventsource.sharedworker.ts` to `Map`.
This commit is contained in:
parent
09a0041965
commit
c0e80dbe26
|
@ -22,6 +22,7 @@
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"stripInternal": true,
|
"stripInternal": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noPropertyAccessFromIndexSignature": false,
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
|
|
@ -25,10 +25,9 @@ const body = computed(() => {
|
||||||
const root = ref<HTMLElement | null>(null);
|
const root = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
root.value.addEventListener('ce-load-context-popup', (e: CustomEvent) => {
|
root.value.addEventListener('ce-load-context-popup', (e: CustomEventInit<IssuePathInfo>) => {
|
||||||
const data: IssuePathInfo = e.detail;
|
|
||||||
if (!loading.value && issue.value === null) {
|
if (!loading.value && issue.value === null) {
|
||||||
load(data);
|
load(e.detail);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {showTemporaryTooltip} from '../modules/tippy.ts';
|
import {showTemporaryTooltip} from '../modules/tippy.ts';
|
||||||
import {toAbsoluteUrl} from '../utils.ts';
|
import {toAbsoluteUrl} from '../utils.ts';
|
||||||
import {clippie} from 'clippie';
|
import {clippie} from 'clippie';
|
||||||
|
import type {DOMEvent} from '../utils/dom.ts';
|
||||||
|
|
||||||
const {copy_success, copy_error} = window.config.i18n;
|
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-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
|
// - data-clipboard-text-type: When set to 'url' will convert relative to absolute urls
|
||||||
export function initGlobalCopyToClipboardListener() {
|
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]');
|
const target = e.target.closest('[data-clipboard-text], [data-clipboard-target]');
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {createTippy} from '../modules/tippy.ts';
|
import {createTippy} from '../modules/tippy.ts';
|
||||||
|
import type {DOMEvent} from '../utils/dom.ts';
|
||||||
|
|
||||||
export async function initColorPickers() {
|
export async function initColorPickers() {
|
||||||
const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input');
|
const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input');
|
||||||
|
@ -37,7 +38,7 @@ function initPicker(el: HTMLElement): void {
|
||||||
updateSquare(square, e.detail.value);
|
updateSquare(square, e.detail.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('input', (e: Event & {target: HTMLInputElement}) => {
|
input.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => {
|
||||||
updateSquare(square, e.target.value);
|
updateSquare(square, e.target.value);
|
||||||
updatePicker(picker, e.target.value);
|
updatePicker(picker, e.target.value);
|
||||||
});
|
});
|
||||||
|
@ -55,8 +56,8 @@ function initPicker(el: HTMLElement): void {
|
||||||
});
|
});
|
||||||
|
|
||||||
// init precolors
|
// init precolors
|
||||||
for (const colorEl of el.querySelectorAll('.precolors .color')) {
|
for (const colorEl of el.querySelectorAll<HTMLElement>('.precolors .color')) {
|
||||||
colorEl.addEventListener('click', (e: MouseEvent & {target: HTMLAnchorElement}) => {
|
colorEl.addEventListener('click', (e: DOMEvent<MouseEvent, HTMLAnchorElement>) => {
|
||||||
const newValue = e.target.getAttribute('data-color-hex');
|
const newValue = e.target.getAttribute('data-color-hex');
|
||||||
input.value = newValue;
|
input.value = newValue;
|
||||||
input.dispatchEvent(new Event('input', {bubbles: true}));
|
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.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';
|
import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
|
|
||||||
export function initGlobalFormDirtyLeaveConfirm() {
|
export function initGlobalFormDirtyLeaveConfirm() {
|
||||||
|
@ -13,7 +13,7 @@ export function initGlobalFormDirtyLeaveConfirm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initGlobalEnterQuickSubmit() {
|
export function initGlobalEnterQuickSubmit() {
|
||||||
document.addEventListener('keydown', (e: KeyboardEvent & {target: HTMLElement}) => {
|
document.addEventListener('keydown', (e: DOMEvent<KeyboardEvent>) => {
|
||||||
if (e.key !== 'Enter') return;
|
if (e.key !== 'Enter') return;
|
||||||
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
|
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
|
||||||
if (hasCtrlOrMeta && e.target.matches('textarea')) {
|
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 = {
|
type CropperOpts = {
|
||||||
container: HTMLElement,
|
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;
|
const files = e.target.files;
|
||||||
if (files?.length > 0) {
|
if (files?.length > 0) {
|
||||||
currentFileName = files[0].name;
|
currentFileName = files[0].name;
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {POST} from '../../modules/fetch.ts';
|
import {POST} from '../../modules/fetch.ts';
|
||||||
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
||||||
|
import type {DOMEvent} from '../../utils/dom.ts';
|
||||||
|
|
||||||
export function initCompReactionSelector(parent: ParentNode = document) {
|
export function initCompReactionSelector(parent: ParentNode = document) {
|
||||||
for (const container of parent.querySelectorAll('.issue-content, .diff-file-body')) {
|
for (const container of parent.querySelectorAll<HTMLElement>('.issue-content, .diff-file-body')) {
|
||||||
container.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
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
|
// 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');
|
const target = e.target.closest('.comment-reaction-button');
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
const sourcesByUrl = {};
|
|
||||||
const sourcesByPort = {};
|
|
||||||
|
|
||||||
class Source {
|
class Source {
|
||||||
url: string;
|
url: string;
|
||||||
eventSource: EventSource;
|
eventSource: EventSource;
|
||||||
listening: Record<string, any>;
|
listening: Record<string, boolean>;
|
||||||
clients: Array<any>;
|
clients: Array<MessagePort>;
|
||||||
|
|
||||||
constructor(url) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.eventSource = new EventSource(url);
|
this.eventSource = new EventSource(url);
|
||||||
this.listening = {};
|
this.listening = {};
|
||||||
|
@ -20,7 +17,7 @@ class Source {
|
||||||
this.listen('error');
|
this.listen('error');
|
||||||
}
|
}
|
||||||
|
|
||||||
register(port) {
|
register(port: MessagePort) {
|
||||||
if (this.clients.includes(port)) return;
|
if (this.clients.includes(port)) return;
|
||||||
|
|
||||||
this.clients.push(port);
|
this.clients.push(port);
|
||||||
|
@ -31,7 +28,7 @@ class Source {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deregister(port) {
|
deregister(port: MessagePort) {
|
||||||
const portIdx = this.clients.indexOf(port);
|
const portIdx = this.clients.indexOf(port);
|
||||||
if (portIdx < 0) {
|
if (portIdx < 0) {
|
||||||
return this.clients.length;
|
return this.clients.length;
|
||||||
|
@ -47,7 +44,7 @@ class Source {
|
||||||
this.eventSource = null;
|
this.eventSource = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(eventType) {
|
listen(eventType: string) {
|
||||||
if (this.listening[eventType]) return;
|
if (this.listening[eventType]) return;
|
||||||
this.listening[eventType] = true;
|
this.listening[eventType] = true;
|
||||||
this.eventSource.addEventListener(eventType, (event) => {
|
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) {
|
for (const client of this.clients) {
|
||||||
client.postMessage(event);
|
client.postMessage(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
status(port) {
|
status(port: MessagePort) {
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
type: 'status',
|
type: 'status',
|
||||||
message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,
|
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) {
|
for (const port of e.ports) {
|
||||||
port.addEventListener('message', (event) => {
|
port.addEventListener('message', (event) => {
|
||||||
if (!self.EventSource) {
|
if (!self.EventSource) {
|
||||||
|
@ -84,14 +85,14 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => {
|
||||||
}
|
}
|
||||||
if (event.data.type === 'start') {
|
if (event.data.type === 'start') {
|
||||||
const url = event.data.url;
|
const url = event.data.url;
|
||||||
if (sourcesByUrl[url]) {
|
if (sourcesByUrl.get(url)) {
|
||||||
// we have a Source registered to this url
|
// we have a Source registered to this url
|
||||||
const source = sourcesByUrl[url];
|
const source = sourcesByUrl.get(url);
|
||||||
source.register(port);
|
source.register(port);
|
||||||
sourcesByPort[port] = source;
|
sourcesByPort.set(port, source);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let source = sourcesByPort[port];
|
let source = sourcesByPort.get(port);
|
||||||
if (source) {
|
if (source) {
|
||||||
if (source.eventSource && source.url === url) return;
|
if (source.eventSource && source.url === url) return;
|
||||||
|
|
||||||
|
@ -101,30 +102,30 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => {
|
||||||
// Clean-up
|
// Clean-up
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
source.close();
|
source.close();
|
||||||
sourcesByUrl[source.url] = null;
|
sourcesByUrl.set(source.url, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create a new Source
|
// Create a new Source
|
||||||
source = new Source(url);
|
source = new Source(url);
|
||||||
source.register(port);
|
source.register(port);
|
||||||
sourcesByUrl[url] = source;
|
sourcesByUrl.set(url, source);
|
||||||
sourcesByPort[port] = source;
|
sourcesByPort.set(port, source);
|
||||||
} else if (event.data.type === 'listen') {
|
} else if (event.data.type === 'listen') {
|
||||||
const source = sourcesByPort[port];
|
const source = sourcesByPort.get(port);
|
||||||
source.listen(event.data.eventType);
|
source.listen(event.data.eventType);
|
||||||
} else if (event.data.type === 'close') {
|
} else if (event.data.type === 'close') {
|
||||||
const source = sourcesByPort[port];
|
const source = sourcesByPort.get(port);
|
||||||
|
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
|
||||||
const count = source.deregister(port);
|
const count = source.deregister(port);
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
source.close();
|
source.close();
|
||||||
sourcesByUrl[source.url] = null;
|
sourcesByUrl.set(source.url, null);
|
||||||
sourcesByPort[port] = null;
|
sourcesByPort.set(port, null);
|
||||||
}
|
}
|
||||||
} else if (event.data.type === 'status') {
|
} else if (event.data.type === 'status') {
|
||||||
const source = sourcesByPort[port];
|
const source = sourcesByPort.get(port);
|
||||||
if (!source) {
|
if (!source) {
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
type: 'status',
|
type: 'status',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import {GET} from '../modules/fetch.ts';
|
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';
|
import {logoutFromWorker} from '../modules/worker.ts';
|
||||||
|
|
||||||
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
|
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
|
||||||
|
@ -25,8 +25,8 @@ export function initNotificationsTable() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// mark clicked unread links for deletion on bfcache restore
|
// mark clicked unread links for deletion on bfcache restore
|
||||||
for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) {
|
for (const link of table.querySelectorAll<HTMLAnchorElement>('.notifications-item[data-status="1"] .notifications-link')) {
|
||||||
link.addEventListener('click', (e : MouseEvent & {target: HTMLElement}) => {
|
link.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
||||||
e.target.closest('.notifications-item').setAttribute('data-remove', 'true');
|
e.target.closest('.notifications-item').setAttribute('data-remove', 'true');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import type {DOMEvent} from '../utils/dom.ts';
|
||||||
|
|
||||||
export function initOAuth2SettingsDisableCheckbox() {
|
export function initOAuth2SettingsDisableCheckbox() {
|
||||||
for (const el of document.querySelectorAll('.disable-setting')) {
|
for (const el of document.querySelectorAll<HTMLInputElement>('.disable-setting')) {
|
||||||
el.addEventListener('change', (e: Event & {target: HTMLInputElement}) => {
|
el.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => {
|
||||||
document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked);
|
document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {GET} from '../modules/fetch.ts';
|
||||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||||
|
|
||||||
export function initRepoGraphGit() {
|
export function initRepoGraphGit() {
|
||||||
const graphContainer = document.querySelector('#git-graph-container');
|
const graphContainer = document.querySelector<HTMLElement>('#git-graph-container');
|
||||||
if (!graphContainer) return;
|
if (!graphContainer) return;
|
||||||
|
|
||||||
document.querySelector('#flow-color-monochrome')?.addEventListener('click', () => {
|
document.querySelector('#flow-color-monochrome')?.addEventListener('click', () => {
|
||||||
|
@ -87,7 +87,7 @@ export function initRepoGraphGit() {
|
||||||
fomanticQuery(flowSelectRefsDropdown).dropdown({
|
fomanticQuery(flowSelectRefsDropdown).dropdown({
|
||||||
clearable: true,
|
clearable: true,
|
||||||
fullTextSeach: 'exact',
|
fullTextSeach: 'exact',
|
||||||
onRemove(toRemove) {
|
onRemove(toRemove: string) {
|
||||||
if (toRemove === '...flow-hide-pr-refs') {
|
if (toRemove === '...flow-hide-pr-refs') {
|
||||||
params.delete('hide-pr-refs');
|
params.delete('hide-pr-refs');
|
||||||
} else {
|
} else {
|
||||||
|
@ -101,7 +101,7 @@ export function initRepoGraphGit() {
|
||||||
}
|
}
|
||||||
updateGraph();
|
updateGraph();
|
||||||
},
|
},
|
||||||
onAdd(toAdd) {
|
onAdd(toAdd: string) {
|
||||||
if (toAdd === '...flow-hide-pr-refs') {
|
if (toAdd === '...flow-hide-pr-refs') {
|
||||||
params.set('hide-pr-refs', 'true');
|
params.set('hide-pr-refs', 'true');
|
||||||
} else {
|
} 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')) {
|
if (e.target.matches('#rev-list li')) {
|
||||||
const flow = e.target.getAttribute('data-flow');
|
const flow = e.target.getAttribute('data-flow');
|
||||||
if (flow === '0') return;
|
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')) {
|
if (e.target.matches('#rev-list li')) {
|
||||||
const flow = e.target.getAttribute('data-flow');
|
const flow = e.target.getAttribute('data-flow');
|
||||||
if (flow === '0') return;
|
if (flow === '0') return;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {stripTags} from '../utils.ts';
|
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 {POST} from '../modules/fetch.ts';
|
||||||
import {showErrorToast, type Toast} from '../modules/toast.ts';
|
import {showErrorToast, type Toast} from '../modules/toast.ts';
|
||||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||||
|
@ -28,7 +28,7 @@ export function initRepoTopicBar() {
|
||||||
mgrBtn.focus();
|
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();
|
lastErrorToast?.hideToast();
|
||||||
const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value;
|
const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value;
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
queryElems,
|
queryElems,
|
||||||
showElem,
|
showElem,
|
||||||
toggleElem,
|
toggleElem,
|
||||||
|
type DOMEvent,
|
||||||
} from '../utils/dom.ts';
|
} from '../utils/dom.ts';
|
||||||
import {setFileFolding} from './file-fold.ts';
|
import {setFileFolding} from './file-fold.ts';
|
||||||
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
|
@ -53,7 +54,7 @@ export function initRepoIssueSidebarList() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initRepoIssueLabelFilter(elDropdown: Element) {
|
function initRepoIssueLabelFilter(elDropdown: HTMLElement) {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
const showArchivedLabels = url.searchParams.get('archived_labels') === 'true';
|
const showArchivedLabels = url.searchParams.get('archived_labels') === 'true';
|
||||||
const queryLabels = url.searchParams.get('labels') || '';
|
const queryLabels = url.searchParams.get('labels') || '';
|
||||||
|
@ -125,7 +126,7 @@ export function initRepoIssueFilterItemLabel() {
|
||||||
|
|
||||||
export function initRepoIssueCommentDelete() {
|
export function initRepoIssueCommentDelete() {
|
||||||
// Delete comment
|
// Delete comment
|
||||||
document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
|
||||||
if (!e.target.matches('.delete-comment')) return;
|
if (!e.target.matches('.delete-comment')) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -200,7 +201,7 @@ export function initRepoIssueDependencyDelete() {
|
||||||
|
|
||||||
export function initRepoIssueCodeCommentCancel() {
|
export function initRepoIssueCodeCommentCancel() {
|
||||||
// Cancel inline code comment
|
// 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;
|
if (!e.target.matches('.cancel-code-comment')) return;
|
||||||
|
|
||||||
const form = e.target.closest('form');
|
const form = e.target.closest('form');
|
||||||
|
@ -222,7 +223,7 @@ export function initRepoPullRequestUpdate() {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const redirect = this.getAttribute('data-redirect');
|
const redirect = this.getAttribute('data-redirect');
|
||||||
this.classList.add('is-loading');
|
this.classList.add('is-loading');
|
||||||
let response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await POST(this.getAttribute('data-do'));
|
response = await POST(this.getAttribute('data-do'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -230,7 +231,7 @@ export function initRepoPullRequestUpdate() {
|
||||||
} finally {
|
} finally {
|
||||||
this.classList.remove('is-loading');
|
this.classList.remove('is-loading');
|
||||||
}
|
}
|
||||||
let data;
|
let data: Record<string, any>;
|
||||||
try {
|
try {
|
||||||
data = await response?.json(); // the response is probably not a JSON
|
data = await response?.json(); // the response is probably not a JSON
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -341,7 +342,7 @@ export function initRepoIssueWipTitle() {
|
||||||
export function initRepoIssueComments() {
|
export function initRepoIssueComments() {
|
||||||
if (!$('.repository.view.issue .timeline').length) return;
|
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');
|
const urlTarget = document.querySelector(':target');
|
||||||
if (!urlTarget) return;
|
if (!urlTarget) return;
|
||||||
|
|
||||||
|
@ -589,7 +590,7 @@ export function initRepoIssueTitleEdit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoIssueBranchSelect() {
|
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]');
|
const el = e.target.closest('.item[data-branch]');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
const pullTargetBranch = document.querySelector('#pull-target-branch');
|
const pullTargetBranch = document.querySelector('#pull-target-branch');
|
||||||
|
@ -628,10 +629,10 @@ function initIssueTemplateCommentEditors($commentForm) {
|
||||||
// * new issue with issue template
|
// * new issue with issue template
|
||||||
const $comboFields = $commentForm.find('.combo-editor-dropzone');
|
const $comboFields = $commentForm.find('.combo-editor-dropzone');
|
||||||
|
|
||||||
const initCombo = async (elCombo) => {
|
const initCombo = async (elCombo: HTMLElement) => {
|
||||||
const $formField = $(elCombo.querySelector('.form-field-real'));
|
const $formField = $(elCombo.querySelector('.form-field-real'));
|
||||||
const dropzoneContainer = elCombo.querySelector('.form-field-dropzone');
|
const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone');
|
||||||
const markdownEditor = elCombo.querySelector('.combo-markdown-editor');
|
const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor');
|
||||||
|
|
||||||
const editor = await initComboMarkdownEditor(markdownEditor);
|
const editor = await initComboMarkdownEditor(markdownEditor);
|
||||||
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value()));
|
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value()));
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {hideElem, showElem} from '../utils/dom.ts';
|
import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
||||||
|
|
||||||
export function initRepoRelease() {
|
export function initRepoRelease() {
|
||||||
document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => {
|
document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
||||||
if (e.target.matches('.remove-rel-attach')) {
|
if (e.target.matches('.remove-rel-attach')) {
|
||||||
const uuid = e.target.getAttribute('data-uuid');
|
const uuid = e.target.getAttribute('data-uuid');
|
||||||
const id = e.target.getAttribute('data-id');
|
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
|
hideTargetInput(tagNameInput); // update on page load because the input may have a value
|
||||||
tagNameInput.addEventListener('input', (e: InputEvent & {target: HTMLInputElement}) => {
|
tagNameInput.addEventListener('input', (e) => {
|
||||||
hideTargetInput(e.target);
|
hideTargetInput(e.target as HTMLInputElement);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import type {DOMEvent} from '../utils/dom.ts';
|
||||||
|
|
||||||
export function initRepositorySearch() {
|
export function initRepositorySearch() {
|
||||||
const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form');
|
const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form');
|
||||||
if (!repositorySearchForm) return;
|
if (!repositorySearchForm) return;
|
||||||
|
|
||||||
repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => {
|
repositorySearchForm.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
|
@ -122,7 +122,7 @@ function initRepoSettingsBranches() {
|
||||||
function initRepoSettingsOptions() {
|
function initRepoSettingsOptions() {
|
||||||
if ($('.repository.settings.options').length > 0) {
|
if ($('.repository.settings.options').length > 0) {
|
||||||
// Enable or select internal/external wiki system and issue tracker.
|
// 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) {
|
if (this.checked) {
|
||||||
$($(this).data('target')).removeClass('disabled');
|
$($(this).data('target')).removeClass('disabled');
|
||||||
if (!$(this).data('context')) $($(this).data('context')).addClass('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');
|
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') {
|
if (this.value === 'false') {
|
||||||
$($(this).data('target')).addClass('disabled');
|
$($(this).data('target')).addClass('disabled');
|
||||||
if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');
|
if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');
|
||||||
|
|
|
@ -13,7 +13,7 @@ async function initRepoWikiFormEditor() {
|
||||||
let editor: ComboMarkdownEditor;
|
let editor: ComboMarkdownEditor;
|
||||||
|
|
||||||
let renderRequesting = false;
|
let renderRequesting = false;
|
||||||
let lastContent;
|
let lastContent: string = '';
|
||||||
const renderEasyMDEPreview = async function () {
|
const renderEasyMDEPreview = async function () {
|
||||||
if (renderRequesting) return;
|
if (renderRequesting) return;
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,13 @@ window.htmx.config.requestClass = 'is-loading';
|
||||||
window.htmx.config.scrollIntoViewOnBoost = false;
|
window.htmx.config.scrollIntoViewOnBoost = false;
|
||||||
|
|
||||||
// https://htmx.org/events/#htmx:sendError
|
// https://htmx.org/events/#htmx:sendError
|
||||||
document.body.addEventListener('htmx:sendError', (event: HtmxEvent) => {
|
document.body.addEventListener('htmx:sendError', (event: Partial<HtmxEvent>) => {
|
||||||
// TODO: add translations
|
// TODO: add translations
|
||||||
showErrorToast(`Network error when calling ${event.detail.requestConfig.path}`);
|
showErrorToast(`Network error when calling ${event.detail.requestConfig.path}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://htmx.org/events/#htmx:responseError
|
// https://htmx.org/events/#htmx:responseError
|
||||||
document.body.addEventListener('htmx:responseError', (event: HtmxEvent) => {
|
document.body.addEventListener('htmx:responseError', (event: Partial<HtmxEvent>) => {
|
||||||
// TODO: add translations
|
// TODO: add translations
|
||||||
showErrorToast(`Error ${event.detail.xhr.status} when calling ${event.detail.requestConfig.path}`);
|
showErrorToast(`Error ${event.detail.xhr.status} when calling ${event.detail.requestConfig.path}`);
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ type ProcessorContext = {
|
||||||
|
|
||||||
function prepareProcessors(ctx:ProcessorContext): Processors {
|
function prepareProcessors(ctx:ProcessorContext): Processors {
|
||||||
const processors = {
|
const processors = {
|
||||||
H1(el: HTMLHeadingElement) {
|
H1(el: HTMLElement) {
|
||||||
const level = parseInt(el.tagName.slice(1));
|
const level = parseInt(el.tagName.slice(1));
|
||||||
el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`;
|
el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`;
|
||||||
},
|
},
|
||||||
|
@ -25,7 +25,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors {
|
||||||
DEL(el: HTMLElement) {
|
DEL(el: HTMLElement) {
|
||||||
return `~~${el.textContent}~~`;
|
return `~~${el.textContent}~~`;
|
||||||
},
|
},
|
||||||
A(el: HTMLAnchorElement) {
|
A(el: HTMLElement) {
|
||||||
const text = el.textContent || 'link';
|
const text = el.textContent || 'link';
|
||||||
const href = el.getAttribute('href');
|
const href = el.getAttribute('href');
|
||||||
if (/^https?:/.test(text) && text === href) {
|
if (/^https?:/.test(text) && text === href) {
|
||||||
|
@ -33,7 +33,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors {
|
||||||
}
|
}
|
||||||
return href ? `[${text}](${href})` : text;
|
return href ? `[${text}](${href})` : text;
|
||||||
},
|
},
|
||||||
IMG(el: HTMLImageElement) {
|
IMG(el: HTMLElement) {
|
||||||
const alt = el.getAttribute('alt') || 'image';
|
const alt = el.getAttribute('alt') || 'image';
|
||||||
const src = el.getAttribute('src');
|
const src = el.getAttribute('src');
|
||||||
const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : '';
|
const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : '';
|
||||||
|
@ -43,7 +43,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors {
|
||||||
}
|
}
|
||||||
return `![${alt}](${src})`;
|
return `![${alt}](${src})`;
|
||||||
},
|
},
|
||||||
P(el: HTMLParagraphElement) {
|
P(el: HTMLElement) {
|
||||||
el.textContent = `${el.textContent}\n`;
|
el.textContent = `${el.textContent}\n`;
|
||||||
},
|
},
|
||||||
BLOCKQUOTE(el: HTMLElement) {
|
BLOCKQUOTE(el: HTMLElement) {
|
||||||
|
@ -54,14 +54,14 @@ function prepareProcessors(ctx:ProcessorContext): Processors {
|
||||||
el.textContent = `${preNewLine}${el.textContent}\n`;
|
el.textContent = `${preNewLine}${el.textContent}\n`;
|
||||||
},
|
},
|
||||||
LI(el: HTMLElement) {
|
LI(el: HTMLElement) {
|
||||||
const parent = el.parentNode;
|
const parent = el.parentNode as HTMLElement;
|
||||||
const bullet = (parent as HTMLElement).tagName === 'OL' ? `1. ` : '* ';
|
const bullet = parent.tagName === 'OL' ? `1. ` : '* ';
|
||||||
const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1);
|
const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1);
|
||||||
el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`;
|
el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`;
|
||||||
return el;
|
return el;
|
||||||
},
|
},
|
||||||
INPUT(el: HTMLInputElement) {
|
INPUT(el: HTMLElement) {
|
||||||
return el.checked ? '[x] ' : '[ ] ';
|
return (el as HTMLInputElement).checked ? '[x] ' : '[ ] ';
|
||||||
},
|
},
|
||||||
CODE(el: HTMLElement) {
|
CODE(el: HTMLElement) {
|
||||||
const text = el.textContent;
|
const text = el.textContent;
|
||||||
|
|
|
@ -121,14 +121,14 @@ function switchTitleToTooltip(target: Element): void {
|
||||||
* Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)"
|
* Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)"
|
||||||
* The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy
|
* The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy
|
||||||
*/
|
*/
|
||||||
function lazyTooltipOnMouseHover(e: MouseEvent): void {
|
function lazyTooltipOnMouseHover(e: Event): void {
|
||||||
e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
|
e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
|
||||||
attachTooltip(this);
|
attachTooltip(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate the tooltip for current element.
|
// Activate the tooltip for current element.
|
||||||
// If the element has no aria-label, use the tooltip content as aria-label.
|
// If the element has no aria-label, use the tooltip content as aria-label.
|
||||||
function attachLazyTooltip(el: Element): void {
|
function attachLazyTooltip(el: HTMLElement): void {
|
||||||
el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true});
|
el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true});
|
||||||
|
|
||||||
// meanwhile, if the element has no aria-label, use the tooltip content as aria-label
|
// meanwhile, if the element has no aria-label, use the tooltip content as aria-label
|
||||||
|
@ -141,8 +141,8 @@ function attachLazyTooltip(el: Element): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate the tooltip for all children elements.
|
// Activate the tooltip for all children elements.
|
||||||
function attachChildrenLazyTooltip(target: Element): void {
|
function attachChildrenLazyTooltip(target: HTMLElement): void {
|
||||||
for (const el of target.querySelectorAll<Element>('[data-tooltip-content]')) {
|
for (const el of target.querySelectorAll<HTMLElement>('[data-tooltip-content]')) {
|
||||||
attachLazyTooltip(el);
|
attachLazyTooltip(el);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ export function initGlobalTooltips(): void {
|
||||||
for (const mutation of [...mutationList, ...pending]) {
|
for (const mutation of [...mutationList, ...pending]) {
|
||||||
if (mutation.type === 'childList') {
|
if (mutation.type === 'childList') {
|
||||||
// mainly for Vue components and AJAX rendered elements
|
// mainly for Vue components and AJAX rendered elements
|
||||||
for (const el of mutation.addedNodes as NodeListOf<Element>) {
|
for (const el of mutation.addedNodes as NodeListOf<HTMLElement>) {
|
||||||
if (!isDocumentFragmentOrElementNode(el)) continue;
|
if (!isDocumentFragmentOrElementNode(el)) continue;
|
||||||
attachChildrenLazyTooltip(el);
|
attachChildrenLazyTooltip(el);
|
||||||
if (el.hasAttribute('data-tooltip-content')) {
|
if (el.hasAttribute('data-tooltip-content')) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ type ArrayLikeIterable<T> = ArrayLike<T> & Iterable<T>; // for NodeListOf and Ar
|
||||||
type ElementArg = Element | string | ArrayLikeIterable<Element> | ReturnType<typeof $>;
|
type ElementArg = Element | string | ArrayLikeIterable<Element> | ReturnType<typeof $>;
|
||||||
type ElementsCallback<T extends Element> = (el: T) => Promisable<any>;
|
type ElementsCallback<T extends Element> = (el: T) => Promisable<any>;
|
||||||
type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>;
|
type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>;
|
||||||
|
export type DOMEvent<E extends Event, T extends Element = HTMLElement> = E & { target: Partial<T>; };
|
||||||
|
|
||||||
function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) {
|
function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) {
|
||||||
if (typeof el === 'string' || el instanceof String) {
|
if (typeof el === 'string' || el instanceof String) {
|
||||||
|
@ -87,7 +88,7 @@ export function queryElemChildren<T extends Element>(parent: Element | ParentNod
|
||||||
|
|
||||||
// it works like parent.querySelectorAll: all descendants are selected
|
// it works like parent.querySelectorAll: all descendants are selected
|
||||||
// in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent
|
// in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent
|
||||||
export function queryElems<T extends Element>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
|
export function queryElems<T extends HTMLElement>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
|
||||||
return applyElemsCallback<T>(parent.querySelectorAll(selector), fn);
|
return applyElemsCallback<T>(parent.querySelectorAll(selector), fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue