From f860fe31d96a7331b429e11ac96d2f166cc5dca0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 12 Nov 2023 15:15:00 +0800 Subject: [PATCH] Move some JS code from `fomantic.js` to standalone files (#27994) To improve maintainability, this PR: 1. Rename `web_src/js/modules/aria` to `web_src/js/modules/fomantic` (the code there are all for aria of fomantic) 2. Move api/transition related code to `web_src/js/modules/fomantic/api.js` and `web_src/js/modules/fomantic/transition.js` No logic is changed. --- web_src/js/modules/fomantic.js | 99 ++----------------- web_src/js/modules/fomantic/api.js | 40 ++++++++ web_src/js/modules/{aria => fomantic}/aria.md | 0 web_src/js/modules/{aria => fomantic}/base.js | 0 .../js/modules/{aria => fomantic}/checkbox.js | 0 .../js/modules/{aria => fomantic}/dropdown.js | 0 .../js/modules/{aria => fomantic}/modal.js | 0 web_src/js/modules/fomantic/transition.js | 54 ++++++++++ 8 files changed, 100 insertions(+), 93 deletions(-) create mode 100644 web_src/js/modules/fomantic/api.js rename web_src/js/modules/{aria => fomantic}/aria.md (100%) rename web_src/js/modules/{aria => fomantic}/base.js (100%) rename web_src/js/modules/{aria => fomantic}/checkbox.js (100%) rename web_src/js/modules/{aria => fomantic}/dropdown.js (100%) rename web_src/js/modules/{aria => fomantic}/modal.js (100%) create mode 100644 web_src/js/modules/fomantic/transition.js diff --git a/web_src/js/modules/fomantic.js b/web_src/js/modules/fomantic.js index da693e9a93..0c7a7ae641 100644 --- a/web_src/js/modules/fomantic.js +++ b/web_src/js/modules/fomantic.js @@ -1,7 +1,9 @@ import $ from 'jquery'; -import {initAriaCheckboxPatch} from './aria/checkbox.js'; -import {initAriaDropdownPatch} from './aria/dropdown.js'; -import {initAriaModalPatch} from './aria/modal.js'; +import {initFomanticApiPatch} from './fomantic/api.js'; +import {initAriaCheckboxPatch} from './fomantic/checkbox.js'; +import {initAriaDropdownPatch} from './fomantic/dropdown.js'; +import {initAriaModalPatch} from './fomantic/modal.js'; +import {initFomanticTransition} from './fomantic/transition.js'; import {svg} from '../svg.js'; export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)'); @@ -22,57 +24,7 @@ export function initGiteaFomantic() { return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`); }; - const transitionNopBehaviors = new Set([ - 'clear queue', 'stop', 'stop all', 'destroy', - 'force repaint', 'repaint', 'reset', - 'looping', 'remove looping', 'disable', 'enable', - 'set duration', 'save conditions', 'restore conditions', - ]); - // stand-in for removed transition module - $.fn.transition = function (arg0, arg1, arg2) { - 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; - if (typeof arg0 === 'string') { - // many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage - if (transitionNopBehaviors.has(arg0)) return this; - // now, the arg0 is an animation name, the syntax: (animation, duration, complete) - argObj = {animation: arg0, ...(arg1 && {duration: arg1}), ...(arg2 && {onComplete: arg2})}; - } else if (typeof arg0 === 'object') { - argObj = arg0; - } else { - throw new Error(`invalid argument: ${arg0}`); - } - - const isAnimationIn = argObj.animation?.startsWith('show') || argObj.animation?.endsWith(' in'); - const isAnimationOut = argObj.animation?.startsWith('hide') || argObj.animation?.endsWith(' out'); - this.each((_, el) => { - let toShow = isAnimationIn; - if (!isAnimationIn && !isAnimationOut) { - // If the animation is not in/out, then it must be a toggle animation. - // Fomantic uses computed styles to check "visibility", but to avoid unnecessary arguments, here it only checks the class. - toShow = this.hasClass('hidden'); // maybe it could also check "!this.hasClass('visible')", leave it to the future until there is a real problem. - } - argObj.onStart?.call(el); - if (toShow) { - el.classList.remove('hidden'); - el.classList.add('visible', 'transition'); - if (argObj.displayType) el.style.setProperty('display', argObj.displayType, 'important'); - argObj.onShow?.call(el); - } else { - el.classList.add('hidden'); - el.classList.remove('visible'); // don't remove the transition class because the Fomantic animation style is `.hidden.transition`. - el.style.removeProperty('display'); - argObj.onHidden?.call(el); - } - argObj.onComplete?.call(el); - }); - return this; - }; - + initFomanticTransition(); initFomanticApiPatch(); // Use the patches to improve accessibility, these patches are designed to be as independent as possible, make it easy to modify or remove in the future. @@ -80,42 +32,3 @@ export function initGiteaFomantic() { initAriaDropdownPatch(); initAriaModalPatch(); } - -function initFomanticApiPatch() { - // - // Fomantic API module has some very buggy behaviors: - // - // If encodeParameters=true, it calls `urlEncodedValue` to encode the parameter. - // However, `urlEncodedValue` just tries to "guess" whether the parameter is already encoded, by decoding the parameter and encoding it again. - // - // There are 2 problems: - // 1. It may guess wrong, and skip encoding a parameter which looks like encoded. - // 2. If the parameter can't be decoded, `decodeURIComponent` will throw an error, and the whole request will fail. - // - // This patch only fixes the second error behavior at the moment. - // - const patchKey = '_giteaFomanticApiPatch'; - const oldApi = $.api; - $.api = $.fn.api = function(...args) { - const apiCall = oldApi.bind(this); - const ret = oldApi.apply(this, args); - - if (typeof args[0] !== 'string') { - const internalGet = apiCall('internal', 'get'); - if (!internalGet.urlEncodedValue[patchKey]) { - const oldUrlEncodedValue = internalGet.urlEncodedValue; - internalGet.urlEncodedValue = function (value) { - try { - return oldUrlEncodedValue(value); - } catch { - // if Fomantic API module's `urlEncodedValue` throws an error, we encode it by ourselves. - return encodeURIComponent(value); - } - }; - internalGet.urlEncodedValue[patchKey] = true; - } - } - return ret; - }; - $.api.settings = oldApi.settings; -} diff --git a/web_src/js/modules/fomantic/api.js b/web_src/js/modules/fomantic/api.js new file mode 100644 index 0000000000..ca212c9fef --- /dev/null +++ b/web_src/js/modules/fomantic/api.js @@ -0,0 +1,40 @@ +import $ from 'jquery'; + +export function initFomanticApiPatch() { + // + // Fomantic API module has some very buggy behaviors: + // + // If encodeParameters=true, it calls `urlEncodedValue` to encode the parameter. + // However, `urlEncodedValue` just tries to "guess" whether the parameter is already encoded, by decoding the parameter and encoding it again. + // + // There are 2 problems: + // 1. It may guess wrong, and skip encoding a parameter which looks like encoded. + // 2. If the parameter can't be decoded, `decodeURIComponent` will throw an error, and the whole request will fail. + // + // This patch only fixes the second error behavior at the moment. + // + const patchKey = '_giteaFomanticApiPatch'; + const oldApi = $.api; + $.api = $.fn.api = function(...args) { + const apiCall = oldApi.bind(this); + const ret = oldApi.apply(this, args); + + if (typeof args[0] !== 'string') { + const internalGet = apiCall('internal', 'get'); + if (!internalGet.urlEncodedValue[patchKey]) { + const oldUrlEncodedValue = internalGet.urlEncodedValue; + internalGet.urlEncodedValue = function (value) { + try { + return oldUrlEncodedValue(value); + } catch { + // if Fomantic API module's `urlEncodedValue` throws an error, we encode it by ourselves. + return encodeURIComponent(value); + } + }; + internalGet.urlEncodedValue[patchKey] = true; + } + } + return ret; + }; + $.api.settings = oldApi.settings; +} diff --git a/web_src/js/modules/aria/aria.md b/web_src/js/modules/fomantic/aria.md similarity index 100% rename from web_src/js/modules/aria/aria.md rename to web_src/js/modules/fomantic/aria.md diff --git a/web_src/js/modules/aria/base.js b/web_src/js/modules/fomantic/base.js similarity index 100% rename from web_src/js/modules/aria/base.js rename to web_src/js/modules/fomantic/base.js diff --git a/web_src/js/modules/aria/checkbox.js b/web_src/js/modules/fomantic/checkbox.js similarity index 100% rename from web_src/js/modules/aria/checkbox.js rename to web_src/js/modules/fomantic/checkbox.js diff --git a/web_src/js/modules/aria/dropdown.js b/web_src/js/modules/fomantic/dropdown.js similarity index 100% rename from web_src/js/modules/aria/dropdown.js rename to web_src/js/modules/fomantic/dropdown.js diff --git a/web_src/js/modules/aria/modal.js b/web_src/js/modules/fomantic/modal.js similarity index 100% rename from web_src/js/modules/aria/modal.js rename to web_src/js/modules/fomantic/modal.js diff --git a/web_src/js/modules/fomantic/transition.js b/web_src/js/modules/fomantic/transition.js new file mode 100644 index 0000000000..78aa0538b0 --- /dev/null +++ b/web_src/js/modules/fomantic/transition.js @@ -0,0 +1,54 @@ +import $ from 'jquery'; + +export function initFomanticTransition() { + const transitionNopBehaviors = new Set([ + 'clear queue', 'stop', 'stop all', 'destroy', + 'force repaint', 'repaint', 'reset', + 'looping', 'remove looping', 'disable', 'enable', + 'set duration', 'save conditions', 'restore conditions', + ]); + // stand-in for removed transition module + $.fn.transition = function (arg0, arg1, arg2) { + 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; + if (typeof arg0 === 'string') { + // many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage + if (transitionNopBehaviors.has(arg0)) return this; + // now, the arg0 is an animation name, the syntax: (animation, duration, complete) + argObj = {animation: arg0, ...(arg1 && {duration: arg1}), ...(arg2 && {onComplete: arg2})}; + } else if (typeof arg0 === 'object') { + argObj = arg0; + } else { + throw new Error(`invalid argument: ${arg0}`); + } + + const isAnimationIn = argObj.animation?.startsWith('show') || argObj.animation?.endsWith(' in'); + const isAnimationOut = argObj.animation?.startsWith('hide') || argObj.animation?.endsWith(' out'); + this.each((_, el) => { + let toShow = isAnimationIn; + if (!isAnimationIn && !isAnimationOut) { + // If the animation is not in/out, then it must be a toggle animation. + // Fomantic uses computed styles to check "visibility", but to avoid unnecessary arguments, here it only checks the class. + toShow = this.hasClass('hidden'); // maybe it could also check "!this.hasClass('visible')", leave it to the future until there is a real problem. + } + argObj.onStart?.call(el); + if (toShow) { + el.classList.remove('hidden'); + el.classList.add('visible', 'transition'); + if (argObj.displayType) el.style.setProperty('display', argObj.displayType, 'important'); + argObj.onShow?.call(el); + } else { + el.classList.add('hidden'); + el.classList.remove('visible'); // don't remove the transition class because the Fomantic animation style is `.hidden.transition`. + el.style.removeProperty('display'); + argObj.onHidden?.call(el); + } + argObj.onComplete?.call(el); + }); + return this; + }; +}