From cabcca3d818e1c7fdfd4ddf41e233f273b779481 Mon Sep 17 00:00:00 2001 From: Jason Song Date: Fri, 26 Jul 2024 07:26:41 +0800 Subject: [PATCH 1/6] Run `detectWebAuthnSupport` only if necessary (#31691) Follow #31676, which is not correct, see https://github.com/go-gitea/gitea/pull/31676#issuecomment-2246658217 Fix #31675, regression of #31504. --- web_src/js/features/user-auth-webauthn.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/user-auth-webauthn.ts b/web_src/js/features/user-auth-webauthn.ts index 35c6aa8f7c..610b559833 100644 --- a/web_src/js/features/user-auth-webauthn.ts +++ b/web_src/js/features/user-auth-webauthn.ts @@ -5,7 +5,9 @@ import {GET, POST} from '../modules/fetch.ts'; const {appSubUrl} = window.config; export async function initUserAuthWebAuthn() { - if (!document.querySelector('.user.signin')) { + const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); + const elSignInPasskeyBtn = document.querySelector('.signin-passkey'); + if (!elPrompt && !elSignInPasskeyBtn) { return; } @@ -13,12 +15,10 @@ export async function initUserAuthWebAuthn() { return; } - const elSignInPasskeyBtn = document.querySelector('.signin-passkey'); if (elSignInPasskeyBtn) { elSignInPasskeyBtn.addEventListener('click', loginPasskey); } - const elPrompt = document.querySelector('.user.signin.webauthn-prompt'); if (elPrompt) { login2FA(); } From 930ca92d7ce80e8b0bdaf92e495026baf2a1d419 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 26 Jul 2024 01:31:24 +0200 Subject: [PATCH 2/6] Add types to fetch,toast,bootstrap,svg (#31627) Reduce `tsc` error count by 53. None of the changes has any runtime effect. --- types.d.ts | 4 ++++ web_src/js/bootstrap.ts | 25 ++++++++----------------- web_src/js/modules/fetch.ts | 16 +++++++++------- web_src/js/modules/toast.ts | 26 +++++++++++++++++++++----- web_src/js/svg.ts | 10 ++++++---- web_src/js/types.ts | 8 ++++++++ 6 files changed, 56 insertions(+), 33 deletions(-) diff --git a/types.d.ts b/types.d.ts index ddfe90407f..3da7cbe050 100644 --- a/types.d.ts +++ b/types.d.ts @@ -10,6 +10,10 @@ interface Window { $: typeof import('@types/jquery'), jQuery: typeof import('@types/jquery'), htmx: typeof import('htmx.org'), + _globalHandlerErrors: Array & { + _inited: boolean, + push: (e: ErrorEvent & PromiseRejectionEvent) => void | number, + }, } declare module 'htmx.org/dist/htmx.esm.js' { diff --git a/web_src/js/bootstrap.ts b/web_src/js/bootstrap.ts index 26627dfded..9e41673b86 100644 --- a/web_src/js/bootstrap.ts +++ b/web_src/js/bootstrap.ts @@ -1,12 +1,13 @@ // DO NOT IMPORT window.config HERE! // to make sure the error handler always works, we should never import `window.config`, because // some user's custom template breaks it. +import type {Intent} from './types.ts'; // This sets up the URL prefix used in webpack's chunk loading. // This file must be imported before any lazy-loading is being attempted. __webpack_public_path__ = `${window.config?.assetUrlPrefix ?? '/assets'}/`; -function shouldIgnoreError(err) { +function shouldIgnoreError(err: Error) { const ignorePatterns = [ '/assets/js/monaco.', // https://github.com/go-gitea/gitea/issues/30861 , https://github.com/microsoft/monaco-editor/issues/4496 ]; @@ -16,14 +17,14 @@ function shouldIgnoreError(err) { return false; } -export function showGlobalErrorMessage(msg, msgType = 'error') { +export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') { const msgContainer = document.querySelector('.page-content') ?? document.body; const msgCompact = msg.replace(/\W/g, '').trim(); // compact the message to a data attribute to avoid too many duplicated messages - let msgDiv = msgContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`); + let msgDiv = msgContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`); if (!msgDiv) { const el = document.createElement('div'); el.innerHTML = `
`; - msgDiv = el.childNodes[0]; + msgDiv = el.childNodes[0] as HTMLDivElement; } // merge duplicated messages into "the message (count)" format const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1; @@ -33,18 +34,7 @@ export function showGlobalErrorMessage(msg, msgType = 'error') { msgContainer.prepend(msgDiv); } -/** - * @param {ErrorEvent|PromiseRejectionEvent} event - Event - * @param {string} event.message - Only present on ErrorEvent - * @param {string} event.error - Only present on ErrorEvent - * @param {string} event.type - Only present on ErrorEvent - * @param {string} event.filename - Only present on ErrorEvent - * @param {number} event.lineno - Only present on ErrorEvent - * @param {number} event.colno - Only present on ErrorEvent - * @param {string} event.reason - Only present on PromiseRejectionEvent - * @param {number} event.promise - Only present on PromiseRejectionEvent - */ -function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}) { +function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}: ErrorEvent & PromiseRejectionEvent) { const err = error ?? reason; const assetBaseUrl = String(new URL(__webpack_public_path__, window.location.origin)); const {runModeIsProd} = window.config ?? {}; @@ -90,7 +80,8 @@ function initGlobalErrorHandler() { } // then, change _globalHandlerErrors to an object with push method, to process further error // events directly - window._globalHandlerErrors = {_inited: true, push: (e) => processWindowErrorEvent(e)}; + // @ts-expect-error -- this should be refactored to not use a fake array + window._globalHandlerErrors = {_inited: true, push: (e: ErrorEvent & PromiseRejectionEvent) => processWindowErrorEvent(e)}; } initGlobalErrorHandler(); diff --git a/web_src/js/modules/fetch.ts b/web_src/js/modules/fetch.ts index b70a4cb304..90ff718498 100644 --- a/web_src/js/modules/fetch.ts +++ b/web_src/js/modules/fetch.ts @@ -1,4 +1,5 @@ import {isObject} from '../utils.ts'; +import type {RequestData, RequestOpts} from '../types.ts'; const {csrfToken} = window.config; @@ -8,8 +9,9 @@ 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, {method = 'GET', data, headers = {}, ...other} = {}) { - let body, contentType; +export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}) { + let body: RequestData; + let contentType: string; if (data instanceof FormData || data instanceof URLSearchParams) { body = data; } else if (isObject(data) || Array.isArray(data)) { @@ -34,8 +36,8 @@ export function request(url, {method = 'GET', data, headers = {}, ...other} = {} }); } -export const GET = (url, opts) => request(url, {method: 'GET', ...opts}); -export const POST = (url, opts) => request(url, {method: 'POST', ...opts}); -export const PATCH = (url, opts) => request(url, {method: 'PATCH', ...opts}); -export const PUT = (url, opts) => request(url, {method: 'PUT', ...opts}); -export const DELETE = (url, opts) => request(url, {method: 'DELETE', ...opts}); +export const GET = (url: string, opts?: RequestOpts) => request(url, {method: 'GET', ...opts}); +export const POST = (url: string, opts?: RequestOpts) => request(url, {method: 'POST', ...opts}); +export const PATCH = (url: string, opts?: RequestOpts) => request(url, {method: 'PATCH', ...opts}); +export const PUT = (url: string, opts?: RequestOpts) => request(url, {method: 'PUT', ...opts}); +export const DELETE = (url: string, opts?: RequestOpts) => request(url, {method: 'DELETE', ...opts}); diff --git a/web_src/js/modules/toast.ts b/web_src/js/modules/toast.ts index cded48e6c2..264ccbbdce 100644 --- a/web_src/js/modules/toast.ts +++ b/web_src/js/modules/toast.ts @@ -2,8 +2,19 @@ import {htmlEscape} from 'escape-goat'; import {svg} from '../svg.ts'; import {animateOnce, showElem} from '../utils/dom.ts'; import Toastify from 'toastify-js'; // don't use "async import", because when network error occurs, the "async import" also fails and nothing is shown +import type {Intent} from '../types.ts'; +import type {SvgName} from '../svg.ts'; +import type {Options} from 'toastify-js'; -const levels = { +type ToastLevels = { + [intent in Intent]: { + icon: SvgName, + background: string, + duration: number, + } +} + +const levels: ToastLevels = { info: { icon: 'octicon-check', background: 'var(--color-green)', @@ -21,8 +32,13 @@ const levels = { }, }; +type ToastOpts = { + useHtmlBody?: boolean, + preventDuplicates?: boolean, +} & Options; + // See https://github.com/apvarun/toastify-js#api for options -function showToast(message, level, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other} = {}) { +function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}) { const body = useHtmlBody ? String(message) : htmlEscape(message); const key = `${level}-${body}`; @@ -59,14 +75,14 @@ function showToast(message, level, {gravity, position, duration, useHtmlBody, pr return toast; } -export function showInfoToast(message, opts) { +export function showInfoToast(message: string, opts?: ToastOpts) { return showToast(message, 'info', opts); } -export function showWarningToast(message, opts) { +export function showWarningToast(message: string, opts?: ToastOpts) { return showToast(message, 'warning', opts); } -export function showErrorToast(message, opts) { +export function showErrorToast(message: string, opts?: ToastOpts) { return showToast(message, 'error', opts); } diff --git a/web_src/js/svg.ts b/web_src/js/svg.ts index a0fe52a7be..6a4bfafc92 100644 --- a/web_src/js/svg.ts +++ b/web_src/js/svg.ts @@ -146,17 +146,19 @@ const svgs = { 'octicon-x-circle-fill': octiconXCircleFill, }; +export type SvgName = keyof typeof svgs; + // TODO: use a more general approach to access SVG icons. // At the moment, developers must check, pick and fill the names manually, // most of the SVG icons in assets couldn't be used directly. // retrieve an HTML string for given SVG icon name, size and additional classes -export function svg(name, size = 16, className = '') { +export function svg(name: SvgName, size = 16, className = '') { if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`); if (size === 16 && !className) return svgs[name]; const document = parseDom(svgs[name], 'image/svg+xml'); - const svgNode = document.firstChild; + const svgNode = document.firstChild as SVGElement; if (size !== 16) { svgNode.setAttribute('width', String(size)); svgNode.setAttribute('height', String(size)); @@ -165,7 +167,7 @@ export function svg(name, size = 16, className = '') { return serializeXml(svgNode); } -export function svgParseOuterInner(name) { +export function svgParseOuterInner(name: SvgName) { const svgStr = svgs[name]; if (!svgStr) throw new Error(`Unknown SVG icon: ${name}`); @@ -179,7 +181,7 @@ export function svgParseOuterInner(name) { const svgInnerHtml = svgStr.slice(p1 + 1, p2); const svgOuterHtml = svgStr.slice(0, p1 + 1) + svgStr.slice(p2); const svgDoc = parseDom(svgOuterHtml, 'image/svg+xml'); - const svgOuter = svgDoc.firstChild; + const svgOuter = svgDoc.firstChild as SVGElement; return {svgOuter, svgInnerHtml}; } diff --git a/web_src/js/types.ts b/web_src/js/types.ts index f69aa92768..3bd1c072a8 100644 --- a/web_src/js/types.ts +++ b/web_src/js/types.ts @@ -21,3 +21,11 @@ export type Config = { mermaidMaxSourceCharacters: number, i18n: Record, } + +export type Intent = 'error' | 'warning' | 'info'; + +export type RequestData = string | FormData | URLSearchParams; + +export type RequestOpts = { + data?: RequestData, +} & RequestInit; From 4b376a0ed934ba77d91ab182215fcff07b13c8df Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 26 Jul 2024 18:00:07 +0800 Subject: [PATCH 3/6] Support `pull_request_target` event for commit status (#31703) Fix [act_runner #573](https://gitea.com/gitea/act_runner/issues/573) Before: ![image](https://github.com/user-attachments/assets/3944bf7f-7a60-4801-bcb3-5e158a180fda) After: ![image](https://github.com/user-attachments/assets/cadac944-40bd-4537-a9d9-e702b8bc1ece) --- services/actions/commit_status.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index eb031511f6..8d86ec4dfa 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" user_model "code.gitea.io/gitea/models/user" + actions_module "code.gitea.io/gitea/modules/actions" git "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" @@ -54,7 +55,11 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er } sha = payload.HeadCommit.ID case webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequestSync: - event = "pull_request" + if run.TriggerEvent == actions_module.GithubEventPullRequestTarget { + event = "pull_request_target" + } else { + event = "pull_request" + } payload, err := run.GetPullRequestEventPayload() if err != nil { return fmt.Errorf("GetPullRequestEventPayload: %w", err) From e1cf760d2f0ba0abe6810fdade69b924f6fdbe1b Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Fri, 26 Jul 2024 21:51:45 +0200 Subject: [PATCH 4/6] OIDC: case-insensitive comparison for auth scheme `Basic` (#31706) @kylef pointed out on https://github.com/go-gitea/gitea/pull/31632 that [RFC7617](https://www.rfc-editor.org/rfc/rfc7617.html#section-2) mandates case-insensitive comparison of the scheme field `Basic`. #31632 copied a case-sensitive comparison from https://github.com/go-gitea/gitea/pull/6293. This PR fixes both comparisons. The issue only affects OIDC, since the implementation for normal Gitea endpoints is already correct: https://github.com/go-gitea/gitea/blob/930ca92d7ce80e8b0bdaf92e495026baf2a1d419/services/auth/basic.go#L55-L58 --- routers/web/auth/oauth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 7988dc96a4..c61a0a6240 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -327,7 +327,7 @@ func getOAuthGroupsForUser(ctx go_context.Context, user *user_model.User) ([]str func parseBasicAuth(ctx *context.Context) (username, password string, err error) { authHeader := ctx.Req.Header.Get("Authorization") - if authType, authData, ok := strings.Cut(authHeader, " "); ok && authType == "Basic" { + if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") { return base.BasicAuthDecode(authData) } return "", "", errors.New("invalid basic authentication") @@ -661,7 +661,7 @@ func AccessTokenOAuth(ctx *context.Context) { // if there is no ClientID or ClientSecret in the request body, fill these fields by the Authorization header and ensure the provided field matches the Authorization header if form.ClientID == "" || form.ClientSecret == "" { authHeader := ctx.Req.Header.Get("Authorization") - if authType, authData, ok := strings.Cut(authHeader, " "); ok && authType == "Basic" { + if authType, authData, ok := strings.Cut(authHeader, " "); ok && strings.EqualFold(authType, "Basic") { clientID, clientSecret, err := base.BasicAuthDecode(authData) if err != nil { handleAccessTokenError(ctx, AccessTokenError{ From a40192dc124f153274d49936a03fd1996140f917 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sat, 27 Jul 2024 00:27:00 +0000 Subject: [PATCH 5/6] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-PT.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 71d018d687..550fbcdaf8 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -2981,6 +2981,10 @@ emails.not_updated=Falhou a modificação do endereço de email solicitado: %v emails.duplicate_active=Este endereço de email já está a ser usado por outro utilizador. emails.change_email_header=Modificar propriedades do email emails.change_email_text=Tem a certeza que quer modificar este endereço de email? +emails.delete=Eliminar email +emails.delete_desc=Tem a certeza que quer eliminar este endereço de email? +emails.deletion_success=O endereço de email foi eliminado. +emails.delete_primary_email_error=Não pode eliminar o email principal. orgs.org_manage_panel=Gestão das organizações orgs.name=Nome From aa36989bd00e8069c0a546c4753e533cefa06a7f Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 27 Jul 2024 16:44:41 +0200 Subject: [PATCH 6/6] Enable `no-jquery/no-parse-html-literal` and fix violation (#31684) Tested it, path segment creation works just like before. --- .eslintrc.yaml | 2 +- web_src/js/features/repo-editor.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 65c897c913..1664e43083 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -568,7 +568,7 @@ rules: no-jquery/no-param: [2] no-jquery/no-parent: [0] no-jquery/no-parents: [2] - no-jquery/no-parse-html-literal: [0] + no-jquery/no-parse-html-literal: [2] no-jquery/no-parse-html: [2] no-jquery/no-parse-json: [2] no-jquery/no-parse-xml: [2] diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts index 8bf23fc60e..dadbd802bc 100644 --- a/web_src/js/features/repo-editor.ts +++ b/web_src/js/features/repo-editor.ts @@ -1,7 +1,7 @@ import $ from 'jquery'; import {htmlEscape} from 'escape-goat'; import {createCodeEditor} from './codeeditor.ts'; -import {hideElem, queryElems, showElem} from '../utils/dom.ts'; +import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts'; import {initMarkupContent} from '../markup/content.ts'; import {attachRefIssueContextPopup} from './contextpopup.ts'; import {POST} from '../modules/fetch.ts'; @@ -61,7 +61,7 @@ export function initRepoEditor() { }); } - const filenameInput = document.querySelector('#file-name'); + const filenameInput = document.querySelector('#file-name'); function joinTreePath() { const parts = []; for (const el of document.querySelectorAll('.breadcrumb span.section')) { @@ -80,8 +80,12 @@ export function initRepoEditor() { const value = parts[i]; if (i < parts.length - 1) { if (value.length) { - $(`${htmlEscape(value)}`).insertBefore($(filenameInput)); - $('').insertBefore($(filenameInput)); + filenameInput.before(createElementFromHTML( + `${htmlEscape(value)}`, + )); + filenameInput.before(createElementFromHTML( + ``, + )); } } else { filenameInput.value = value;