mirror of
https://github.com/go-gitea/gitea
synced 2024-12-22 16:44:26 +00: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-this-before-super: [2]
|
||||||
no-throw-literal: [2]
|
no-throw-literal: [2]
|
||||||
no-undef-init: [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-undefined: [0]
|
||||||
no-underscore-dangle: [0]
|
no-underscore-dangle: [0]
|
||||||
no-unexpected-multiline: [2]
|
no-unexpected-multiline: [2]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export function initAutoFocusEnd() {
|
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.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);
|
el.setSelectionRange(el.value.length, el.value.length);
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,11 @@ export async function initCaptcha() {
|
|||||||
}
|
}
|
||||||
case 'm-captcha': {
|
case 'm-captcha': {
|
||||||
const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue');
|
const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue');
|
||||||
|
// @ts-expect-error
|
||||||
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
||||||
const instanceURL = captchaEl.getAttribute('data-instance-url');
|
const instanceURL = captchaEl.getAttribute('data-instance-url');
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
mCaptcha.default({
|
mCaptcha.default({
|
||||||
siteKey: {
|
siteKey: {
|
||||||
instanceUrl: new URL(instanceURL),
|
instanceUrl: new URL(instanceURL),
|
||||||
|
@ -3,7 +3,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
|
|||||||
|
|
||||||
const {pageData} = window.config;
|
const {pageData} = window.config;
|
||||||
|
|
||||||
async function initInputCitationValue(citationCopyApa, citationCopyBibtex) {
|
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
|
||||||
const [{Cite, plugins}] = await Promise.all([
|
const [{Cite, plugins}] = await Promise.all([
|
||||||
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
|
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
|
||||||
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
|
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
|
||||||
@ -27,9 +27,9 @@ export async function initCitationFileCopyContent() {
|
|||||||
|
|
||||||
if (!pageData.citationFileContent) return;
|
if (!pageData.citationFileContent) return;
|
||||||
|
|
||||||
const citationCopyApa = document.querySelector('#citation-copy-apa');
|
const citationCopyApa = document.querySelector<HTMLButtonElement>('#citation-copy-apa');
|
||||||
const citationCopyBibtex = document.querySelector('#citation-copy-bibtex');
|
const citationCopyBibtex = document.querySelector<HTMLButtonElement>('#citation-copy-bibtex');
|
||||||
const inputContent = document.querySelector('#citation-copy-content');
|
const inputContent = document.querySelector<HTMLInputElement>('#citation-copy-content');
|
||||||
|
|
||||||
if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return;
|
if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return;
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export async function initCitationFileCopyContent() {
|
|||||||
citationCopyApa.classList.toggle('primary', !isBibtex);
|
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');
|
const dropdownBtn = e.target.closest('.ui.dropdown.button');
|
||||||
dropdownBtn.classList.add('is-loading');
|
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-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) => {
|
document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
|
||||||
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;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ export function initGlobalCopyToClipboardListener() {
|
|||||||
|
|
||||||
let text = target.getAttribute('data-clipboard-text');
|
let text = target.getAttribute('data-clipboard-text');
|
||||||
if (!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') {
|
if (text && target.getAttribute('data-clipboard-text-type') === 'url') {
|
||||||
|
@ -21,7 +21,7 @@ const baseOptions = {
|
|||||||
automaticLayout: true,
|
automaticLayout: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getEditorconfig(input) {
|
function getEditorconfig(input: HTMLInputElement) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(input.getAttribute('data-editorconfig'));
|
return JSON.parse(input.getAttribute('data-editorconfig'));
|
||||||
} catch {
|
} catch {
|
||||||
@ -58,7 +58,7 @@ function exportEditor(editor) {
|
|||||||
if (!window.codeEditors.includes(editor)) window.codeEditors.push(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');
|
const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor');
|
||||||
|
|
||||||
initLanguages(monaco);
|
initLanguages(monaco);
|
||||||
@ -72,7 +72,7 @@ export async function createMonaco(textarea, filename, editorOpts) {
|
|||||||
// https://github.com/microsoft/monaco-editor/issues/2427
|
// 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
|
// also, monaco can only parse 6-digit hex colors, so we convert the colors to that format
|
||||||
const styles = window.getComputedStyle(document.documentElement);
|
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', {
|
monaco.editor.defineTheme('gitea', {
|
||||||
base: isDarkTheme() ? 'vs-dark' : 'vs',
|
base: isDarkTheme() ? 'vs-dark' : 'vs',
|
||||||
@ -127,13 +127,13 @@ export async function createMonaco(textarea, filename, editorOpts) {
|
|||||||
return {monaco, editor};
|
return {monaco, editor};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileBasedOptions(filename, lineWrapExts) {
|
function getFileBasedOptions(filename: string, lineWrapExts: string[]) {
|
||||||
return {
|
return {
|
||||||
wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off',
|
wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePreviewDisplay(previewable) {
|
function togglePreviewDisplay(previewable: boolean) {
|
||||||
const previewTab = document.querySelector('a[data-tab="preview"]');
|
const previewTab = document.querySelector('a[data-tab="preview"]');
|
||||||
if (!previewTab) return;
|
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 filename = basename(filenameInput.value);
|
||||||
const previewableExts = new Set((textarea.getAttribute('data-previewable-extensions') || '').split(','));
|
const previewableExts = new Set((textarea.getAttribute('data-previewable-extensions') || '').split(','));
|
||||||
const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(',');
|
const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(',');
|
||||||
@ -177,10 +177,10 @@ export async function createCodeEditor(textarea, filenameInput) {
|
|||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEditorConfigOptions(ec) {
|
function getEditorConfigOptions(ec: Record<string, any>): Record<string, any> {
|
||||||
if (!isObject(ec)) return {};
|
if (!isObject(ec)) return {};
|
||||||
|
|
||||||
const opts = {};
|
const opts: Record<string, any> = {};
|
||||||
opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec);
|
opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec);
|
||||||
if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size);
|
if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size);
|
||||||
if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize;
|
if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {createTippy} from '../modules/tippy.ts';
|
import {createTippy} from '../modules/tippy.ts';
|
||||||
|
|
||||||
export async function initColorPickers() {
|
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;
|
if (!els.length) return;
|
||||||
|
|
||||||
await Promise.all([
|
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';
|
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);
|
el.setAttribute('color', newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPicker(el) {
|
function initPicker(el: HTMLElement): void {
|
||||||
const input = el.querySelector('input');
|
const input = el.querySelector('input');
|
||||||
|
|
||||||
const square = document.createElement('div');
|
const square = document.createElement('div');
|
||||||
@ -37,7 +37,7 @@ function initPicker(el) {
|
|||||||
updateSquare(square, e.detail.value);
|
updateSquare(square, e.detail.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('input', (e) => {
|
input.addEventListener('input', (e: Event & {target: HTMLInputElement}) => {
|
||||||
updateSquare(square, e.target.value);
|
updateSquare(square, e.target.value);
|
||||||
updatePicker(picker, e.target.value);
|
updatePicker(picker, e.target.value);
|
||||||
});
|
});
|
||||||
@ -56,7 +56,7 @@ function initPicker(el) {
|
|||||||
|
|
||||||
// init precolors
|
// init precolors
|
||||||
for (const colorEl of el.querySelectorAll('.precolors .color')) {
|
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');
|
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}));
|
||||||
|
@ -3,7 +3,7 @@ import {POST} from '../modules/fetch.ts';
|
|||||||
import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
|
import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
|
||||||
import {showErrorToast} from '../modules/toast.ts';
|
import {showErrorToast} from '../modules/toast.ts';
|
||||||
|
|
||||||
export function initGlobalButtonClickOnEnter() {
|
export function initGlobalButtonClickOnEnter(): void {
|
||||||
$(document).on('keypress', 'div.ui.button,span.ui.button', (e) => {
|
$(document).on('keypress', 'div.ui.button,span.ui.button', (e) => {
|
||||||
if (e.code === ' ' || e.code === 'Enter') {
|
if (e.code === ' ' || e.code === 'Enter') {
|
||||||
$(e.target).trigger('click');
|
$(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.
|
// ".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.
|
// 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 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`.
|
// 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.
|
// 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) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
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 `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."`
|
||||||
if (btn.getAttribute('data-type') === 'form') {
|
if (btn.getAttribute('data-type') === 'form') {
|
||||||
const formSelector = btn.getAttribute('data-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`);
|
if (!form) throw new Error(`no form named ${formSelector} found`);
|
||||||
form.submit();
|
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.
|
// 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.
|
// 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")
|
// 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,
|
push: (e: ErrorEvent & PromiseRejectionEvent) => void | number,
|
||||||
},
|
},
|
||||||
__webpack_public_path__: string;
|
__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');
|
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');
|
const els = document.querySelectorAll('.markup code.language-math');
|
||||||
if (!els.length) return;
|
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 observer = new MutationObserver((mutationList) => {
|
||||||
const len = mutationList.length;
|
const len = mutationList.length;
|
||||||
for (let i = 0; i < len; i++) {
|
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
|
// 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
|
// which will automatically set an appropriate headers. For json content, only object
|
||||||
// and array types are currently supported.
|
// 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 body: RequestData;
|
||||||
let contentType: string;
|
let contentType: string;
|
||||||
if (data instanceof FormData || data instanceof URLSearchParams) {
|
if (data instanceof FormData || data instanceof URLSearchParams) {
|
||||||
|
@ -19,7 +19,7 @@ export function initGiteaFomantic() {
|
|||||||
// Do not use "cursor: pointer" for dropdown labels
|
// Do not use "cursor: pointer" for dropdown labels
|
||||||
$.fn.dropdown.settings.className.label += ' tw-cursor-default';
|
$.fn.dropdown.settings.className.label += ' tw-cursor-default';
|
||||||
// Always use Gitea's SVG icons
|
// 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;
|
const escape = $.fn.dropdown.settings.templates.escape;
|
||||||
return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`);
|
return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import type {FomanticInitFunction} from '../../types.ts';
|
||||||
|
|
||||||
export function initFomanticApiPatch() {
|
export function initFomanticApiPatch() {
|
||||||
//
|
//
|
||||||
@ -15,7 +16,7 @@ export function initFomanticApiPatch() {
|
|||||||
//
|
//
|
||||||
const patchKey = '_giteaFomanticApiPatch';
|
const patchKey = '_giteaFomanticApiPatch';
|
||||||
const oldApi = $.api;
|
const oldApi = $.api;
|
||||||
$.api = $.fn.api = function(...args) {
|
$.api = $.fn.api = function(...args: Parameters<FomanticInitFunction>) {
|
||||||
const apiCall = oldApi.bind(this);
|
const apiCall = oldApi.bind(this);
|
||||||
const ret = oldApi.apply(this, args);
|
const ret = oldApi.apply(this, args);
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ export function initFomanticApiPatch() {
|
|||||||
const internalGet = apiCall('internal', 'get');
|
const internalGet = apiCall('internal', 'get');
|
||||||
if (!internalGet.urlEncodedValue[patchKey]) {
|
if (!internalGet.urlEncodedValue[patchKey]) {
|
||||||
const oldUrlEncodedValue = internalGet.urlEncodedValue;
|
const oldUrlEncodedValue = internalGet.urlEncodedValue;
|
||||||
internalGet.urlEncodedValue = function (value) {
|
internalGet.urlEncodedValue = function (value: any) {
|
||||||
try {
|
try {
|
||||||
return oldUrlEncodedValue(value);
|
return oldUrlEncodedValue(value);
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -5,7 +5,7 @@ export function generateAriaId() {
|
|||||||
return `_aria_auto_id_${ariaIdCounter++}`;
|
return `_aria_auto_id_${ariaIdCounter++}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function linkLabelAndInput(label, input) {
|
export function linkLabelAndInput(label: Element, input: Element) {
|
||||||
const labelFor = label.getAttribute('for');
|
const labelFor = label.getAttribute('for');
|
||||||
const inputId = input.getAttribute('id');
|
const inputId = input.getAttribute('id');
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import {queryElemChildren} from '../../utils/dom.ts';
|
|||||||
|
|
||||||
export function initFomanticDimmer() {
|
export function initFomanticDimmer() {
|
||||||
// stand-in for removed dimmer module
|
// stand-in for removed dimmer module
|
||||||
$.fn.dimmer = function (arg0, arg1) {
|
$.fn.dimmer = function (arg0: string, arg1: any) {
|
||||||
if (arg0 === 'add content') {
|
if (arg0 === 'add content') {
|
||||||
const $el = arg1;
|
const $el = arg1;
|
||||||
const existingDimmer = document.querySelector('body > .ui.dimmer');
|
const existingDimmer = document.querySelector('body > .ui.dimmer');
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import {generateAriaId} from './base.ts';
|
import {generateAriaId} from './base.ts';
|
||||||
|
import type {FomanticInitFunction} from '../../types.ts';
|
||||||
|
|
||||||
const ariaPatchKey = '_giteaAriaPatchDropdown';
|
const ariaPatchKey = '_giteaAriaPatchDropdown';
|
||||||
const fomanticDropdownFn = $.fn.dropdown;
|
const fomanticDropdownFn = $.fn.dropdown;
|
||||||
@ -8,13 +9,13 @@ const fomanticDropdownFn = $.fn.dropdown;
|
|||||||
export function initAriaDropdownPatch() {
|
export function initAriaDropdownPatch() {
|
||||||
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
|
if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once');
|
||||||
$.fn.dropdown = ariaDropdownFn;
|
$.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:
|
// 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 does the one-time attaching on the first call
|
||||||
// * it delegates the `onLabelCreate` to the patched `onLabelCreate` to add necessary aria attributes
|
// * 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);
|
const ret = fomanticDropdownFn.apply(this, args);
|
||||||
|
|
||||||
// if the `$().dropdown()` call is without arguments, or it has non-string (object) argument,
|
// 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
|
// 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.
|
// 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();
|
if (!item.id) item.id = generateAriaId();
|
||||||
item.setAttribute('role', dropdown[ariaPatchKey].listItemRole);
|
item.setAttribute('role', dropdown[ariaPatchKey].listItemRole);
|
||||||
item.setAttribute('tabindex', '-1');
|
item.setAttribute('tabindex', '-1');
|
||||||
@ -43,7 +44,7 @@ function updateMenuItem(dropdown, item) {
|
|||||||
* make the label item and its "delete icon" have correct aria attributes
|
* make the label item and its "delete icon" have correct aria attributes
|
||||||
* @param {HTMLElement} label
|
* @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>"
|
// 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) {
|
if (!label.id) {
|
||||||
label.id = generateAriaId();
|
label.id = generateAriaId();
|
||||||
@ -59,7 +60,7 @@ function updateSelectionLabel(label) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delegate the dropdown's template functions and callback functions to add aria attributes.
|
// 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);
|
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.
|
// 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)
|
// the "template" functions are used for dynamic creation (eg: AJAX)
|
||||||
const dropdownTemplates = {...dropdownCall('setting', 'templates'), t: performance.now()};
|
const dropdownTemplates = {...dropdownCall('setting', 'templates'), t: performance.now()};
|
||||||
const dropdownTemplatesMenuOld = dropdownTemplates.menu;
|
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
|
// when the dropdown menu items are loaded from AJAX requests, the items are created dynamically
|
||||||
const menuItems = dropdownTemplatesMenuOld(response, fields, preserveHTML, className);
|
const menuItems = dropdownTemplatesMenuOld(response, fields, preserveHTML, className);
|
||||||
const div = document.createElement('div');
|
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
|
// the `onLabelCreate` is used to add necessary aria attributes for dynamically created selection labels
|
||||||
const dropdownOnLabelCreateOld = dropdownCall('setting', 'onLabelCreate');
|
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);
|
const $label = dropdownOnLabelCreateOld.call(this, value, text);
|
||||||
updateSelectionLabel($label[0]);
|
updateSelectionLabel($label[0]);
|
||||||
return $label;
|
return $label;
|
||||||
@ -97,7 +98,7 @@ function delegateOne($dropdown) {
|
|||||||
|
|
||||||
const oldSet = dropdownCall('internal', 'set');
|
const oldSet = dropdownCall('internal', 'set');
|
||||||
const oldSetDirection = oldSet.direction;
|
const oldSetDirection = oldSet.direction;
|
||||||
oldSet.direction = function($menu) {
|
oldSet.direction = function($menu: any) {
|
||||||
oldSetDirection.call(this, $menu);
|
oldSetDirection.call(this, $menu);
|
||||||
const classNames = dropdownCall('setting', 'className');
|
const classNames = dropdownCall('setting', 'className');
|
||||||
$menu = $menu || $dropdown.find('> .menu');
|
$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
|
// 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
|
// prepare static dropdown menu list popup
|
||||||
if (!menu.id) {
|
if (!menu.id) {
|
||||||
menu.id = generateAriaId();
|
menu.id = generateAriaId();
|
||||||
@ -125,7 +126,7 @@ function attachStaticElements(dropdown, focusable, menu) {
|
|||||||
menu.setAttribute('role', dropdown[ariaPatchKey].listPopupRole);
|
menu.setAttribute('role', dropdown[ariaPatchKey].listPopupRole);
|
||||||
|
|
||||||
// prepare selection label items
|
// prepare selection label items
|
||||||
for (const label of dropdown.querySelectorAll('.ui.label')) {
|
for (const label of dropdown.querySelectorAll<HTMLElement>('.ui.label')) {
|
||||||
updateSelectionLabel(label);
|
updateSelectionLabel(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +143,7 @@ function attachStaticElements(dropdown, focusable, menu) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachInit(dropdown) {
|
function attachInit(dropdown: HTMLElement) {
|
||||||
dropdown[ariaPatchKey] = {};
|
dropdown[ariaPatchKey] = {};
|
||||||
if (dropdown.classList.contains('custom')) return;
|
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.
|
// 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
|
const focusable = textSearch || dropdown; // the primary element for focus, see comment above
|
||||||
if (!focusable) return;
|
if (!focusable) return;
|
||||||
|
|
||||||
@ -191,7 +192,7 @@ function attachInit(dropdown) {
|
|||||||
attachStaticElements(dropdown, focusable, menu);
|
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 showing, it has class: ".animating.in"
|
||||||
// when hiding, it has class: ".visible.animating.out"
|
// when hiding, it has class: ".visible.animating.out"
|
||||||
const isMenuVisible = () => (menu.classList.contains('visible') && !menu.classList.contains('out')) || menu.classList.contains('in');
|
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
|
// 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') {
|
if (e.key === 'Enter') {
|
||||||
const dropdownCall = fomanticDropdownFn.bind($(dropdown));
|
const dropdownCall = fomanticDropdownFn.bind($(dropdown));
|
||||||
@ -260,7 +261,7 @@ function attachDomEvents(dropdown, focusable, menu) {
|
|||||||
deferredRefreshAriaActiveItem(100);
|
deferredRefreshAriaActiveItem(100);
|
||||||
}, 0);
|
}, 0);
|
||||||
}, true);
|
}, true);
|
||||||
dropdown.addEventListener('click', (e) => {
|
dropdown.addEventListener('click', (e: MouseEvent) => {
|
||||||
if (isMenuVisible() &&
|
if (isMenuVisible() &&
|
||||||
ignoreClickPreVisible !== 2 && // dropdown is switch from invisible to visible
|
ignoreClickPreVisible !== 2 && // dropdown is switch from invisible to visible
|
||||||
ignoreClickPreEvents === 2 // the click event is related to mousedown+focus
|
ignoreClickPreEvents === 2 // the click event is related to mousedown+focus
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import type {FomanticInitFunction} from '../../types.ts';
|
||||||
|
|
||||||
const fomanticModalFn = $.fn.modal;
|
const fomanticModalFn = $.fn.modal;
|
||||||
|
|
||||||
@ -6,12 +7,12 @@ const fomanticModalFn = $.fn.modal;
|
|||||||
export function initAriaModalPatch() {
|
export function initAriaModalPatch() {
|
||||||
if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once');
|
if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once');
|
||||||
$.fn.modal = ariaModalFn;
|
$.fn.modal = ariaModalFn;
|
||||||
ariaModalFn.settings = fomanticModalFn.settings;
|
(ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the patched `$.fn.modal` modal function
|
// the patched `$.fn.modal` modal function
|
||||||
// * it does the one-time attaching on the first call
|
// * it does the one-time attaching on the first call
|
||||||
function ariaModalFn(...args) {
|
function ariaModalFn(...args: Parameters<FomanticInitFunction>) {
|
||||||
const ret = fomanticModalFn.apply(this, args);
|
const ret = fomanticModalFn.apply(this, args);
|
||||||
if (args[0] === 'show' || args[0]?.autoShow) {
|
if (args[0] === 'show' || args[0]?.autoShow) {
|
||||||
for (const el of this) {
|
for (const el of this) {
|
||||||
|
@ -8,13 +8,13 @@ export function initFomanticTransition() {
|
|||||||
'set duration', 'save conditions', 'restore conditions',
|
'set duration', 'save conditions', 'restore conditions',
|
||||||
]);
|
]);
|
||||||
// stand-in for removed transition module
|
// 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 supported') return true;
|
||||||
if (arg0 === 'is animating') return false;
|
if (arg0 === 'is animating') return false;
|
||||||
if (arg0 === 'is inward') return false;
|
if (arg0 === 'is inward') return false;
|
||||||
if (arg0 === 'is outward') return false;
|
if (arg0 === 'is outward') return false;
|
||||||
|
|
||||||
let argObj;
|
let argObj: Record<string, any>;
|
||||||
if (typeof arg0 === 'string') {
|
if (typeof arg0 === 'string') {
|
||||||
// many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage
|
// many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage
|
||||||
if (transitionNopBehaviors.has(arg0)) return this;
|
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');
|
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');
|
||||||
|
|
||||||
return new Sortable(el, {
|
return new Sortable(el, {
|
||||||
animation: 150,
|
animation: 150,
|
||||||
ghostClass: 'card-ghost',
|
ghostClass: 'card-ghost',
|
||||||
onChoose: (e) => {
|
onChoose: (e: SortableEvent) => {
|
||||||
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
||||||
handle.classList.add('tw-cursor-grabbing');
|
handle.classList.add('tw-cursor-grabbing');
|
||||||
opts.onChoose?.(e);
|
opts.onChoose?.(e);
|
||||||
},
|
},
|
||||||
onUnchoose: (e) => {
|
onUnchoose: (e: SortableEvent) => {
|
||||||
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
||||||
handle.classList.remove('tw-cursor-grabbing');
|
handle.classList.remove('tw-cursor-grabbing');
|
||||||
opts.onUnchoose?.(e);
|
opts.onUnchoose?.(e);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import tippy, {followCursor} from 'tippy.js';
|
import tippy, {followCursor} from 'tippy.js';
|
||||||
import {isDocumentFragmentOrElementNode} from '../utils/dom.ts';
|
import {isDocumentFragmentOrElementNode} from '../utils/dom.ts';
|
||||||
import {formatDatetime} from '../utils/time.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 = {
|
type TippyOpts = {
|
||||||
role?: string,
|
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
|
// 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;
|
const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts;
|
||||||
|
|
||||||
|
// @ts-expect-error: wrong type derived by typescript
|
||||||
const instance: Instance = tippy(target, {
|
const instance: Instance = tippy(target, {
|
||||||
appendTo: document.body,
|
appendTo: document.body,
|
||||||
animation: false,
|
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.
|
* 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);
|
switchTitleToTooltip(target);
|
||||||
|
|
||||||
content = content ?? target.getAttribute('data-tooltip-content');
|
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 hasClipboardTarget = target.hasAttribute('data-clipboard-target');
|
||||||
const hideOnClick = !hasClipboardTarget;
|
const hideOnClick = !hasClipboardTarget;
|
||||||
|
|
||||||
const props = {
|
const props: TippyOpts = {
|
||||||
content,
|
content,
|
||||||
delay: 100,
|
delay: 100,
|
||||||
role: 'tooltip',
|
role: 'tooltip',
|
||||||
theme: 'tooltip',
|
theme: 'tooltip',
|
||||||
hideOnClick,
|
hideOnClick,
|
||||||
placement: target.getAttribute('data-tooltip-placement') || 'top-start',
|
placement: target.getAttribute('data-tooltip-placement') as Placement || 'top-start',
|
||||||
followCursor: target.getAttribute('data-tooltip-follow-cursor') || false,
|
followCursor: target.getAttribute('data-tooltip-follow-cursor') as Props['followCursor'] || false,
|
||||||
...(target.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true, aria: {content: 'describedby', expanded: false}} : {}),
|
...(target.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true, aria: {content: 'describedby', expanded: false}} : {}),
|
||||||
} as TippyOpts;
|
};
|
||||||
|
|
||||||
if (!target._tippy) {
|
if (!target._tippy) {
|
||||||
createTippy(target, props);
|
createTippy(target, props);
|
||||||
@ -96,7 +97,7 @@ function attachTooltip(target: Element, content: Content = null) {
|
|||||||
return target._tippy;
|
return target._tippy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchTitleToTooltip(target: Element) {
|
function switchTitleToTooltip(target: Element): void {
|
||||||
let title = target.getAttribute('title');
|
let title = target.getAttribute('title');
|
||||||
if (title) {
|
if (title) {
|
||||||
// apply custom formatting to relative-time's tooltips
|
// 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)"
|
* 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) {
|
function lazyTooltipOnMouseHover(e: MouseEvent): 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) {
|
function attachLazyTooltip(el: Element): 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,13 +142,13 @@ function attachLazyTooltip(el: Element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Activate the tooltip for all children elements.
|
// 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]')) {
|
for (const el of target.querySelectorAll<Element>('[data-tooltip-content]')) {
|
||||||
attachLazyTooltip(el);
|
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
|
// use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed
|
||||||
const observerConnect = (observer: MutationObserver) => observer.observe(document, {
|
const observerConnect = (observer: MutationObserver) => observer.observe(document, {
|
||||||
subtree: true,
|
subtree: true,
|
||||||
@ -178,7 +179,7 @@ export function initGlobalTooltips() {
|
|||||||
attachChildrenLazyTooltip(document.documentElement);
|
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
|
// if the target is inside a dropdown, the menu will be hidden soon
|
||||||
// so display the tooltip on the dropdown instead
|
// so display the tooltip on the dropdown instead
|
||||||
target = target.closest('.ui.dropdown') || target;
|
target = target.closest('.ui.dropdown') || target;
|
||||||
|
@ -2,7 +2,7 @@ import {sleep} from '../utils.ts';
|
|||||||
|
|
||||||
const {appSubUrl} = window.config;
|
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
|
// wait for a while because other requests (eg: logout) may be in the flight
|
||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
window.location.href = `${appSubUrl}/`;
|
window.location.href = `${appSubUrl}/`;
|
||||||
|
@ -54,3 +54,8 @@ export type Issue = {
|
|||||||
merged: boolean;
|
merged: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FomanticInitFunction = {
|
||||||
|
settings?: Record<string, any>,
|
||||||
|
(...args: any[]): any,
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import emojis from '../../../assets/emoji.json';
|
import emojis from '../../../assets/emoji.json';
|
||||||
import {GET} from '../modules/fetch.ts';
|
import {GET} from '../modules/fetch.ts';
|
||||||
import type {Issue} from '../features/issue.ts';
|
import type {Issue} from '../types.ts';
|
||||||
|
|
||||||
const maxMatches = 6;
|
const maxMatches = 6;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user