mirror of
https://github.com/go-gitea/gitea.git
synced 2024-11-23 18:49:47 +08:00
Fix some typescript issues (#32586)
Fixes around 30 or so typescript errors. No runtime changes.
This commit is contained in:
parent
9bf821ae6c
commit
675c288811
@ -642,7 +642,7 @@ rules:
|
||||
no-this-before-super: [2]
|
||||
no-throw-literal: [2]
|
||||
no-undef-init: [2]
|
||||
no-undef: [2, {typeof: true}]
|
||||
no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes
|
||||
no-undefined: [0]
|
||||
no-underscore-dangle: [0]
|
||||
no-unexpected-multiline: [2]
|
||||
|
@ -1,5 +1,5 @@
|
||||
export function initAutoFocusEnd() {
|
||||
for (const el of document.querySelectorAll('.js-autofocus-end')) {
|
||||
for (const el of document.querySelectorAll<HTMLInputElement>('.js-autofocus-end')) {
|
||||
el.focus(); // expects only one such element on one page. If there are many, then the last one gets the focus.
|
||||
el.setSelectionRange(el.value.length, el.value.length);
|
||||
}
|
||||
|
@ -35,9 +35,11 @@ export async function initCaptcha() {
|
||||
}
|
||||
case 'm-captcha': {
|
||||
const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue');
|
||||
// @ts-expect-error
|
||||
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
||||
const instanceURL = captchaEl.getAttribute('data-instance-url');
|
||||
|
||||
// @ts-expect-error
|
||||
mCaptcha.default({
|
||||
siteKey: {
|
||||
instanceUrl: new URL(instanceURL),
|
||||
|
@ -3,7 +3,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
|
||||
const {pageData} = window.config;
|
||||
|
||||
async function initInputCitationValue(citationCopyApa, citationCopyBibtex) {
|
||||
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
|
||||
const [{Cite, plugins}] = await Promise.all([
|
||||
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
|
||||
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
|
||||
@ -27,9 +27,9 @@ export async function initCitationFileCopyContent() {
|
||||
|
||||
if (!pageData.citationFileContent) return;
|
||||
|
||||
const citationCopyApa = document.querySelector('#citation-copy-apa');
|
||||
const citationCopyBibtex = document.querySelector('#citation-copy-bibtex');
|
||||
const inputContent = document.querySelector('#citation-copy-content');
|
||||
const citationCopyApa = document.querySelector<HTMLButtonElement>('#citation-copy-apa');
|
||||
const citationCopyBibtex = document.querySelector<HTMLButtonElement>('#citation-copy-bibtex');
|
||||
const inputContent = document.querySelector<HTMLInputElement>('#citation-copy-content');
|
||||
|
||||
if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return;
|
||||
|
||||
@ -41,7 +41,7 @@ export async function initCitationFileCopyContent() {
|
||||
citationCopyApa.classList.toggle('primary', !isBibtex);
|
||||
};
|
||||
|
||||
document.querySelector('#cite-repo-button')?.addEventListener('click', async (e) => {
|
||||
document.querySelector('#cite-repo-button')?.addEventListener('click', async (e: MouseEvent & {target: HTMLAnchorElement}) => {
|
||||
const dropdownBtn = e.target.closest('.ui.dropdown.button');
|
||||
dropdownBtn.classList.add('is-loading');
|
||||
|
||||
|
@ -9,7 +9,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) => {
|
||||
document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
||||
const target = e.target.closest('[data-clipboard-text], [data-clipboard-target]');
|
||||
if (!target) return;
|
||||
|
||||
@ -17,7 +17,7 @@ export function initGlobalCopyToClipboardListener() {
|
||||
|
||||
let text = target.getAttribute('data-clipboard-text');
|
||||
if (!text) {
|
||||
text = document.querySelector(target.getAttribute('data-clipboard-target'))?.value;
|
||||
text = document.querySelector<HTMLInputElement>(target.getAttribute('data-clipboard-target'))?.value;
|
||||
}
|
||||
|
||||
if (text && target.getAttribute('data-clipboard-text-type') === 'url') {
|
||||
|
@ -21,7 +21,7 @@ const baseOptions = {
|
||||
automaticLayout: true,
|
||||
};
|
||||
|
||||
function getEditorconfig(input) {
|
||||
function getEditorconfig(input: HTMLInputElement) {
|
||||
try {
|
||||
return JSON.parse(input.getAttribute('data-editorconfig'));
|
||||
} catch {
|
||||
@ -58,7 +58,7 @@ function exportEditor(editor) {
|
||||
if (!window.codeEditors.includes(editor)) window.codeEditors.push(editor);
|
||||
}
|
||||
|
||||
export async function createMonaco(textarea, filename, editorOpts) {
|
||||
export async function createMonaco(textarea: HTMLTextAreaElement, filename: string, editorOpts: Record<string, any>) {
|
||||
const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor');
|
||||
|
||||
initLanguages(monaco);
|
||||
@ -72,7 +72,7 @@ export async function createMonaco(textarea, filename, editorOpts) {
|
||||
// https://github.com/microsoft/monaco-editor/issues/2427
|
||||
// also, monaco can only parse 6-digit hex colors, so we convert the colors to that format
|
||||
const styles = window.getComputedStyle(document.documentElement);
|
||||
const getColor = (name) => tinycolor(styles.getPropertyValue(name).trim()).toString('hex6');
|
||||
const getColor = (name: string) => tinycolor(styles.getPropertyValue(name).trim()).toString('hex6');
|
||||
|
||||
monaco.editor.defineTheme('gitea', {
|
||||
base: isDarkTheme() ? 'vs-dark' : 'vs',
|
||||
@ -127,13 +127,13 @@ export async function createMonaco(textarea, filename, editorOpts) {
|
||||
return {monaco, editor};
|
||||
}
|
||||
|
||||
function getFileBasedOptions(filename, lineWrapExts) {
|
||||
function getFileBasedOptions(filename: string, lineWrapExts: string[]) {
|
||||
return {
|
||||
wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off',
|
||||
};
|
||||
}
|
||||
|
||||
function togglePreviewDisplay(previewable) {
|
||||
function togglePreviewDisplay(previewable: boolean) {
|
||||
const previewTab = document.querySelector('a[data-tab="preview"]');
|
||||
if (!previewTab) return;
|
||||
|
||||
@ -152,7 +152,7 @@ function togglePreviewDisplay(previewable) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCodeEditor(textarea, filenameInput) {
|
||||
export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameInput: HTMLInputElement) {
|
||||
const filename = basename(filenameInput.value);
|
||||
const previewableExts = new Set((textarea.getAttribute('data-previewable-extensions') || '').split(','));
|
||||
const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(',');
|
||||
@ -177,10 +177,10 @@ export async function createCodeEditor(textarea, filenameInput) {
|
||||
return editor;
|
||||
}
|
||||
|
||||
function getEditorConfigOptions(ec) {
|
||||
function getEditorConfigOptions(ec: Record<string, any>): Record<string, any> {
|
||||
if (!isObject(ec)) return {};
|
||||
|
||||
const opts = {};
|
||||
const opts: Record<string, any> = {};
|
||||
opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec);
|
||||
if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size);
|
||||
if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {createTippy} from '../modules/tippy.ts';
|
||||
|
||||
export async function initColorPickers() {
|
||||
const els = document.querySelectorAll('.js-color-picker-input');
|
||||
const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input');
|
||||
if (!els.length) return;
|
||||
|
||||
await Promise.all([
|
||||
@ -14,15 +14,15 @@ export async function initColorPickers() {
|
||||
}
|
||||
}
|
||||
|
||||
function updateSquare(el, newValue) {
|
||||
function updateSquare(el: HTMLElement, newValue: string): void {
|
||||
el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent';
|
||||
}
|
||||
|
||||
function updatePicker(el, newValue) {
|
||||
function updatePicker(el: HTMLElement, newValue: string): void {
|
||||
el.setAttribute('color', newValue);
|
||||
}
|
||||
|
||||
function initPicker(el) {
|
||||
function initPicker(el: HTMLElement): void {
|
||||
const input = el.querySelector('input');
|
||||
|
||||
const square = document.createElement('div');
|
||||
@ -37,7 +37,7 @@ function initPicker(el) {
|
||||
updateSquare(square, e.detail.value);
|
||||
});
|
||||
|
||||
input.addEventListener('input', (e) => {
|
||||
input.addEventListener('input', (e: Event & {target: HTMLInputElement}) => {
|
||||
updateSquare(square, e.target.value);
|
||||
updatePicker(picker, e.target.value);
|
||||
});
|
||||
@ -56,7 +56,7 @@ function initPicker(el) {
|
||||
|
||||
// init precolors
|
||||
for (const colorEl of el.querySelectorAll('.precolors .color')) {
|
||||
colorEl.addEventListener('click', (e) => {
|
||||
colorEl.addEventListener('click', (e: MouseEvent & {target: HTMLAnchorElement}) => {
|
||||
const newValue = e.target.getAttribute('data-color-hex');
|
||||
input.value = newValue;
|
||||
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
|
@ -3,7 +3,7 @@ import {POST} from '../modules/fetch.ts';
|
||||
import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
|
||||
export function initGlobalButtonClickOnEnter() {
|
||||
export function initGlobalButtonClickOnEnter(): void {
|
||||
$(document).on('keypress', 'div.ui.button,span.ui.button', (e) => {
|
||||
if (e.code === ' ' || e.code === 'Enter') {
|
||||
$(e.target).trigger('click');
|
||||
@ -12,13 +12,13 @@ export function initGlobalButtonClickOnEnter() {
|
||||
});
|
||||
}
|
||||
|
||||
export function initGlobalDeleteButton() {
|
||||
export function initGlobalDeleteButton(): void {
|
||||
// ".delete-button" shows a confirmation modal defined by `data-modal-id` attribute.
|
||||
// Some model/form elements will be filled by `data-id` / `data-name` / `data-data-xxx` attributes.
|
||||
// If there is a form defined by `data-form`, then the form will be submitted as-is (without any modification).
|
||||
// If there is no form, then the data will be posted to `data-url`.
|
||||
// TODO: it's not encouraged to use this method. `show-modal` does far better than this.
|
||||
for (const btn of document.querySelectorAll('.delete-button')) {
|
||||
for (const btn of document.querySelectorAll<HTMLElement>('.delete-button')) {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@ -46,7 +46,7 @@ export function initGlobalDeleteButton() {
|
||||
// if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."`
|
||||
if (btn.getAttribute('data-type') === 'form') {
|
||||
const formSelector = btn.getAttribute('data-form');
|
||||
const form = document.querySelector(formSelector);
|
||||
const form = document.querySelector<HTMLFormElement>(formSelector);
|
||||
if (!form) throw new Error(`no form named ${formSelector} found`);
|
||||
form.submit();
|
||||
}
|
||||
@ -73,7 +73,7 @@ export function initGlobalDeleteButton() {
|
||||
}
|
||||
}
|
||||
|
||||
export function initGlobalButtons() {
|
||||
export function initGlobalButtons(): void {
|
||||
// There are many "cancel button" elements in modal dialogs, Fomantic UI expects they are button-like elements but never submit a form.
|
||||
// However, Gitea misuses the modal dialog and put the cancel buttons inside forms, so we must prevent the form submission.
|
||||
// There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content")
|
||||
|
4
web_src/js/globals.d.ts
vendored
4
web_src/js/globals.d.ts
vendored
@ -58,4 +58,8 @@ interface Window {
|
||||
push: (e: ErrorEvent & PromiseRejectionEvent) => void | number,
|
||||
},
|
||||
__webpack_public_path__: string;
|
||||
grecaptcha: any,
|
||||
turnstile: any,
|
||||
hcaptcha: any,
|
||||
codeEditors: any[],
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ function targetElement(el: Element) {
|
||||
return el.classList.contains('is-loading') ? el : el.closest('pre');
|
||||
}
|
||||
|
||||
export async function renderMath(): void {
|
||||
export async function renderMath(): Promise<void> {
|
||||
const els = document.querySelectorAll('.markup code.language-math');
|
||||
if (!els.length) return;
|
||||
|
||||
|
@ -13,7 +13,7 @@ function attachDirAuto(el: DirElement) {
|
||||
}
|
||||
}
|
||||
|
||||
export function initDirAuto() {
|
||||
export function initDirAuto(): void {
|
||||
const observer = new MutationObserver((mutationList) => {
|
||||
const len = mutationList.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
|
@ -9,7 +9,7 @@ const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']);
|
||||
// fetch wrapper, use below method name functions and the `data` option to pass in data
|
||||
// which will automatically set an appropriate headers. For json content, only object
|
||||
// and array types are currently supported.
|
||||
export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}) {
|
||||
export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}): Promise<Response> {
|
||||
let body: RequestData;
|
||||
let contentType: string;
|
||||
if (data instanceof FormData || data instanceof URLSearchParams) {
|
||||
|
@ -19,7 +19,7 @@ export function initGiteaFomantic() {
|
||||
// Do not use "cursor: pointer" for dropdown labels
|
||||
$.fn.dropdown.settings.className.label += ' tw-cursor-default';
|
||||
// Always use Gitea's SVG icons
|
||||
$.fn.dropdown.settings.templates.label = function(_value, text, preserveHTML, className) {
|
||||
$.fn.dropdown.settings.templates.label = function(_value: any, text: any, preserveHTML: any, className: Record<string, string>) {
|
||||
const escape = $.fn.dropdown.settings.templates.escape;
|
||||
return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`);
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import $ from 'jquery';
|
||||
import type {FomanticInitFunction} from '../../types.ts';
|
||||
|
||||
export function initFomanticApiPatch() {
|
||||
//
|
||||
@ -15,7 +16,7 @@ export function initFomanticApiPatch() {
|
||||
//
|
||||
const patchKey = '_giteaFomanticApiPatch';
|
||||
const oldApi = $.api;
|
||||
$.api = $.fn.api = function(...args) {
|
||||
$.api = $.fn.api = function(...args: Parameters<FomanticInitFunction>) {
|
||||
const apiCall = oldApi.bind(this);
|
||||
const ret = oldApi.apply(this, args);
|
||||
|
||||
@ -23,7 +24,7 @@ export function initFomanticApiPatch() {
|
||||
const internalGet = apiCall('internal', 'get');
|
||||
if (!internalGet.urlEncodedValue[patchKey]) {
|
||||
const oldUrlEncodedValue = internalGet.urlEncodedValue;
|
||||
internalGet.urlEncodedValue = function (value) {
|
||||
internalGet.urlEncodedValue = function (value: any) {
|
||||
try {
|
||||
return oldUrlEncodedValue(value);
|
||||
} catch {
|
||||
|
@ -5,7 +5,7 @@ export function generateAriaId() {
|
||||
return `_aria_auto_id_${ariaIdCounter++}`;
|
||||
}
|
||||
|
||||
export function linkLabelAndInput(label, input) {
|
||||
export function linkLabelAndInput(label: Element, input: Element) {
|
||||
const labelFor = label.getAttribute('for');
|
||||
const inputId = input.getAttribute('id');
|
||||
|
||||
|
@ -3,7 +3,7 @@ import {queryElemChildren} from '../../utils/dom.ts';
|
||||
|
||||
export function initFomanticDimmer() {
|
||||
// stand-in for removed dimmer module
|
||||
$.fn.dimmer = function (arg0, arg1) {
|
||||
$.fn.dimmer = function (arg0: string, arg1: any) {
|
||||
if (arg0 === 'add content') {
|
||||
const $el = arg1;
|
||||
const existingDimmer = document.querySelector('body > .ui.dimmer');
|
||||
|
@ -1,5 +1,6 @@
|
||||
import $ from 'jquery';
|
||||
import {generateAriaId} from './base.ts';
|
||||
import type {FomanticInitFunction} from '../../types.ts';
|
||||
|
||||
const ariaPatchKey = '_giteaAriaPatchDropdown';
|
||||
const fomanticDropdownFn = $.fn.dropdown;
|
||||
@ -8,13 +9,13 @@ const fomanticDropdownFn = $.fn.dropdown;
|
||||
export function initAriaDropdownPatch() {
|
||||
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
|
||||
$.fn.dropdown = ariaDropdownFn;
|
||||
ariaDropdownFn.settings = fomanticDropdownFn.settings;
|
||||
(ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings;
|
||||
}
|
||||
|
||||
// the patched `$.fn.dropdown` function, it passes the arguments to Fomantic's `$.fn.dropdown` function, and:
|
||||
// * it does the one-time attaching on the first call
|
||||
// * it delegates the `onLabelCreate` to the patched `onLabelCreate` to add necessary aria attributes
|
||||
function ariaDropdownFn(...args) {
|
||||
function ariaDropdownFn(...args: Parameters<FomanticInitFunction>) {
|
||||
const ret = fomanticDropdownFn.apply(this, args);
|
||||
|
||||
// if the `$().dropdown()` call is without arguments, or it has non-string (object) argument,
|
||||
@ -33,7 +34,7 @@ function ariaDropdownFn(...args) {
|
||||
|
||||
// make the item has role=option/menuitem, add an id if there wasn't one yet, make items as non-focusable
|
||||
// the elements inside the dropdown menu item should not be focusable, the focus should always be on the dropdown primary element.
|
||||
function updateMenuItem(dropdown, item) {
|
||||
function updateMenuItem(dropdown: HTMLElement, item: HTMLElement) {
|
||||
if (!item.id) item.id = generateAriaId();
|
||||
item.setAttribute('role', dropdown[ariaPatchKey].listItemRole);
|
||||
item.setAttribute('tabindex', '-1');
|
||||
@ -43,7 +44,7 @@ function updateMenuItem(dropdown, item) {
|
||||
* make the label item and its "delete icon" have correct aria attributes
|
||||
* @param {HTMLElement} label
|
||||
*/
|
||||
function updateSelectionLabel(label) {
|
||||
function updateSelectionLabel(label: HTMLElement) {
|
||||
// the "label" is like this: "<a|div class="ui label" data-value="1">the-label-name <i|svg class="delete icon"/></a>"
|
||||
if (!label.id) {
|
||||
label.id = generateAriaId();
|
||||
@ -59,7 +60,7 @@ function updateSelectionLabel(label) {
|
||||
}
|
||||
|
||||
// delegate the dropdown's template functions and callback functions to add aria attributes.
|
||||
function delegateOne($dropdown) {
|
||||
function delegateOne($dropdown: any) {
|
||||
const dropdownCall = fomanticDropdownFn.bind($dropdown);
|
||||
|
||||
// If there is a "search input" in the "menu", Fomantic will only "focus the input" but not "toggle the menu" when the "dropdown icon" is clicked.
|
||||
@ -74,7 +75,7 @@ function delegateOne($dropdown) {
|
||||
// the "template" functions are used for dynamic creation (eg: AJAX)
|
||||
const dropdownTemplates = {...dropdownCall('setting', 'templates'), t: performance.now()};
|
||||
const dropdownTemplatesMenuOld = dropdownTemplates.menu;
|
||||
dropdownTemplates.menu = function(response, fields, preserveHTML, className) {
|
||||
dropdownTemplates.menu = function(response: any, fields: any, preserveHTML: any, className: Record<string, string>) {
|
||||
// when the dropdown menu items are loaded from AJAX requests, the items are created dynamically
|
||||
const menuItems = dropdownTemplatesMenuOld(response, fields, preserveHTML, className);
|
||||
const div = document.createElement('div');
|
||||
@ -89,7 +90,7 @@ function delegateOne($dropdown) {
|
||||
|
||||
// the `onLabelCreate` is used to add necessary aria attributes for dynamically created selection labels
|
||||
const dropdownOnLabelCreateOld = dropdownCall('setting', 'onLabelCreate');
|
||||
dropdownCall('setting', 'onLabelCreate', function(value, text) {
|
||||
dropdownCall('setting', 'onLabelCreate', function(value: any, text: string) {
|
||||
const $label = dropdownOnLabelCreateOld.call(this, value, text);
|
||||
updateSelectionLabel($label[0]);
|
||||
return $label;
|
||||
@ -97,7 +98,7 @@ function delegateOne($dropdown) {
|
||||
|
||||
const oldSet = dropdownCall('internal', 'set');
|
||||
const oldSetDirection = oldSet.direction;
|
||||
oldSet.direction = function($menu) {
|
||||
oldSet.direction = function($menu: any) {
|
||||
oldSetDirection.call(this, $menu);
|
||||
const classNames = dropdownCall('setting', 'className');
|
||||
$menu = $menu || $dropdown.find('> .menu');
|
||||
@ -113,7 +114,7 @@ function delegateOne($dropdown) {
|
||||
}
|
||||
|
||||
// for static dropdown elements (generated by server-side template), prepare them with necessary aria attributes
|
||||
function attachStaticElements(dropdown, focusable, menu) {
|
||||
function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, menu: HTMLElement) {
|
||||
// prepare static dropdown menu list popup
|
||||
if (!menu.id) {
|
||||
menu.id = generateAriaId();
|
||||
@ -125,7 +126,7 @@ function attachStaticElements(dropdown, focusable, menu) {
|
||||
menu.setAttribute('role', dropdown[ariaPatchKey].listPopupRole);
|
||||
|
||||
// prepare selection label items
|
||||
for (const label of dropdown.querySelectorAll('.ui.label')) {
|
||||
for (const label of dropdown.querySelectorAll<HTMLElement>('.ui.label')) {
|
||||
updateSelectionLabel(label);
|
||||
}
|
||||
|
||||
@ -142,7 +143,7 @@ function attachStaticElements(dropdown, focusable, menu) {
|
||||
}
|
||||
}
|
||||
|
||||
function attachInit(dropdown) {
|
||||
function attachInit(dropdown: HTMLElement) {
|
||||
dropdown[ariaPatchKey] = {};
|
||||
if (dropdown.classList.contains('custom')) return;
|
||||
|
||||
@ -161,7 +162,7 @@ function attachInit(dropdown) {
|
||||
|
||||
// TODO: multiple selection is only partially supported. Check and test them one by one in the future.
|
||||
|
||||
const textSearch = dropdown.querySelector('input.search');
|
||||
const textSearch = dropdown.querySelector<HTMLElement>('input.search');
|
||||
const focusable = textSearch || dropdown; // the primary element for focus, see comment above
|
||||
if (!focusable) return;
|
||||
|
||||
@ -191,7 +192,7 @@ function attachInit(dropdown) {
|
||||
attachStaticElements(dropdown, focusable, menu);
|
||||
}
|
||||
|
||||
function attachDomEvents(dropdown, focusable, menu) {
|
||||
function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HTMLElement) {
|
||||
// when showing, it has class: ".animating.in"
|
||||
// when hiding, it has class: ".visible.animating.out"
|
||||
const isMenuVisible = () => (menu.classList.contains('visible') && !menu.classList.contains('out')) || menu.classList.contains('in');
|
||||
@ -215,7 +216,7 @@ function attachDomEvents(dropdown, focusable, menu) {
|
||||
}
|
||||
};
|
||||
|
||||
dropdown.addEventListener('keydown', (e) => {
|
||||
dropdown.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
// here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler
|
||||
if (e.key === 'Enter') {
|
||||
const dropdownCall = fomanticDropdownFn.bind($(dropdown));
|
||||
@ -260,7 +261,7 @@ function attachDomEvents(dropdown, focusable, menu) {
|
||||
deferredRefreshAriaActiveItem(100);
|
||||
}, 0);
|
||||
}, true);
|
||||
dropdown.addEventListener('click', (e) => {
|
||||
dropdown.addEventListener('click', (e: MouseEvent) => {
|
||||
if (isMenuVisible() &&
|
||||
ignoreClickPreVisible !== 2 && // dropdown is switch from invisible to visible
|
||||
ignoreClickPreEvents === 2 // the click event is related to mousedown+focus
|
||||
|
@ -1,4 +1,5 @@
|
||||
import $ from 'jquery';
|
||||
import type {FomanticInitFunction} from '../../types.ts';
|
||||
|
||||
const fomanticModalFn = $.fn.modal;
|
||||
|
||||
@ -6,12 +7,12 @@ const fomanticModalFn = $.fn.modal;
|
||||
export function initAriaModalPatch() {
|
||||
if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once');
|
||||
$.fn.modal = ariaModalFn;
|
||||
ariaModalFn.settings = fomanticModalFn.settings;
|
||||
(ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings;
|
||||
}
|
||||
|
||||
// the patched `$.fn.modal` modal function
|
||||
// * it does the one-time attaching on the first call
|
||||
function ariaModalFn(...args) {
|
||||
function ariaModalFn(...args: Parameters<FomanticInitFunction>) {
|
||||
const ret = fomanticModalFn.apply(this, args);
|
||||
if (args[0] === 'show' || args[0]?.autoShow) {
|
||||
for (const el of this) {
|
||||
|
@ -8,13 +8,13 @@ export function initFomanticTransition() {
|
||||
'set duration', 'save conditions', 'restore conditions',
|
||||
]);
|
||||
// stand-in for removed transition module
|
||||
$.fn.transition = function (arg0, arg1, arg2) {
|
||||
$.fn.transition = function (arg0: any, arg1: any, arg2: any) {
|
||||
if (arg0 === 'is supported') return true;
|
||||
if (arg0 === 'is animating') return false;
|
||||
if (arg0 === 'is inward') return false;
|
||||
if (arg0 === 'is outward') return false;
|
||||
|
||||
let argObj;
|
||||
let argObj: Record<string, any>;
|
||||
if (typeof arg0 === 'string') {
|
||||
// many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage
|
||||
if (transitionNopBehaviors.has(arg0)) return this;
|
||||
|
@ -1,17 +1,18 @@
|
||||
import type {SortableOptions} from 'sortablejs';
|
||||
import type {SortableOptions, SortableEvent} from 'sortablejs';
|
||||
|
||||
export async function createSortable(el, opts: {handle?: string} & SortableOptions = {}) {
|
||||
export async function createSortable(el: HTMLElement, opts: {handle?: string} & SortableOptions = {}) {
|
||||
// @ts-expect-error: wrong type derived by typescript
|
||||
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');
|
||||
|
||||
return new Sortable(el, {
|
||||
animation: 150,
|
||||
ghostClass: 'card-ghost',
|
||||
onChoose: (e) => {
|
||||
onChoose: (e: SortableEvent) => {
|
||||
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
||||
handle.classList.add('tw-cursor-grabbing');
|
||||
opts.onChoose?.(e);
|
||||
},
|
||||
onUnchoose: (e) => {
|
||||
onUnchoose: (e: SortableEvent) => {
|
||||
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
||||
handle.classList.remove('tw-cursor-grabbing');
|
||||
opts.onUnchoose?.(e);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import tippy, {followCursor} from 'tippy.js';
|
||||
import {isDocumentFragmentOrElementNode} from '../utils/dom.ts';
|
||||
import {formatDatetime} from '../utils/time.ts';
|
||||
import type {Content, Instance, Props} from 'tippy.js';
|
||||
import type {Content, Instance, Placement, Props} from 'tippy.js';
|
||||
|
||||
type TippyOpts = {
|
||||
role?: string,
|
||||
@ -16,6 +16,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
|
||||
// because we should use our own wrapper functions to handle them, do not let the user override them
|
||||
const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts;
|
||||
|
||||
// @ts-expect-error: wrong type derived by typescript
|
||||
const instance: Instance = tippy(target, {
|
||||
appendTo: document.body,
|
||||
animation: false,
|
||||
@ -65,7 +66,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
|
||||
*
|
||||
* Note: "tooltip" doesn't equal to "tippy". "tooltip" means a auto-popup content, it just uses tippy as the implementation.
|
||||
*/
|
||||
function attachTooltip(target: Element, content: Content = null) {
|
||||
function attachTooltip(target: Element, content: Content = null): Instance {
|
||||
switchTitleToTooltip(target);
|
||||
|
||||
content = content ?? target.getAttribute('data-tooltip-content');
|
||||
@ -77,16 +78,16 @@ function attachTooltip(target: Element, content: Content = null) {
|
||||
const hasClipboardTarget = target.hasAttribute('data-clipboard-target');
|
||||
const hideOnClick = !hasClipboardTarget;
|
||||
|
||||
const props = {
|
||||
const props: TippyOpts = {
|
||||
content,
|
||||
delay: 100,
|
||||
role: 'tooltip',
|
||||
theme: 'tooltip',
|
||||
hideOnClick,
|
||||
placement: target.getAttribute('data-tooltip-placement') || 'top-start',
|
||||
followCursor: target.getAttribute('data-tooltip-follow-cursor') || false,
|
||||
placement: target.getAttribute('data-tooltip-placement') as Placement || 'top-start',
|
||||
followCursor: target.getAttribute('data-tooltip-follow-cursor') as Props['followCursor'] || false,
|
||||
...(target.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true, aria: {content: 'describedby', expanded: false}} : {}),
|
||||
} as TippyOpts;
|
||||
};
|
||||
|
||||
if (!target._tippy) {
|
||||
createTippy(target, props);
|
||||
@ -96,7 +97,7 @@ function attachTooltip(target: Element, content: Content = null) {
|
||||
return target._tippy;
|
||||
}
|
||||
|
||||
function switchTitleToTooltip(target: Element) {
|
||||
function switchTitleToTooltip(target: Element): void {
|
||||
let title = target.getAttribute('title');
|
||||
if (title) {
|
||||
// apply custom formatting to relative-time's tooltips
|
||||
@ -121,14 +122,14 @@ function switchTitleToTooltip(target: Element) {
|
||||
* 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
|
||||
*/
|
||||
function lazyTooltipOnMouseHover(e: MouseEvent) {
|
||||
function lazyTooltipOnMouseHover(e: MouseEvent): void {
|
||||
e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
|
||||
attachTooltip(this);
|
||||
}
|
||||
|
||||
// Activate the tooltip for current element.
|
||||
// If the element has no aria-label, use the tooltip content as aria-label.
|
||||
function attachLazyTooltip(el: Element) {
|
||||
function attachLazyTooltip(el: Element): void {
|
||||
el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true});
|
||||
|
||||
// meanwhile, if the element has no aria-label, use the tooltip content as aria-label
|
||||
@ -141,13 +142,13 @@ function attachLazyTooltip(el: Element) {
|
||||
}
|
||||
|
||||
// Activate the tooltip for all children elements.
|
||||
function attachChildrenLazyTooltip(target: Element) {
|
||||
function attachChildrenLazyTooltip(target: Element): void {
|
||||
for (const el of target.querySelectorAll<Element>('[data-tooltip-content]')) {
|
||||
attachLazyTooltip(el);
|
||||
}
|
||||
}
|
||||
|
||||
export function initGlobalTooltips() {
|
||||
export function initGlobalTooltips(): void {
|
||||
// use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed
|
||||
const observerConnect = (observer: MutationObserver) => observer.observe(document, {
|
||||
subtree: true,
|
||||
@ -178,7 +179,7 @@ export function initGlobalTooltips() {
|
||||
attachChildrenLazyTooltip(document.documentElement);
|
||||
}
|
||||
|
||||
export function showTemporaryTooltip(target: Element, content: Content) {
|
||||
export function showTemporaryTooltip(target: Element, content: Content): void {
|
||||
// if the target is inside a dropdown, the menu will be hidden soon
|
||||
// so display the tooltip on the dropdown instead
|
||||
target = target.closest('.ui.dropdown') || target;
|
||||
|
@ -2,7 +2,7 @@ import {sleep} from '../utils.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
export async function logoutFromWorker() {
|
||||
export async function logoutFromWorker(): Promise<void> {
|
||||
// wait for a while because other requests (eg: logout) may be in the flight
|
||||
await sleep(5000);
|
||||
window.location.href = `${appSubUrl}/`;
|
||||
|
@ -54,3 +54,8 @@ export type Issue = {
|
||||
merged: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type FomanticInitFunction = {
|
||||
settings?: Record<string, any>,
|
||||
(...args: any[]): any,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import emojis from '../../../assets/emoji.json';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import type {Issue} from '../features/issue.ts';
|
||||
import type {Issue} from '../types.ts';
|
||||
|
||||
const maxMatches = 6;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user