From 8c3d7c049a3540dfccf81a79e33a52b5f3172488 Mon Sep 17 00:00:00 2001
From: silverwind <me@silverwind.io>
Date: Tue, 19 Mar 2024 17:28:46 +0100
Subject: [PATCH] Forbid jQuery `.css` and refactor all usage (#29852)

Tested all functionality. There is a [pre-existing
bug](https://github.com/go-gitea/gitea/issues/29853) when moving a
project panels which is not caused by this refactoring.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
(cherry picked from commit fa100618c4b644346bf5666f92d33dce0747d0a2)
---
 .eslintrc.yaml                          |   4 +-
 web_src/js/features/colorpicker.js      |   8 +-
 web_src/js/features/comp/ColorPicker.js |  16 ++--
 web_src/js/features/comp/LabelEdit.js   |   6 +-
 web_src/js/features/imagediff.js        | 108 ++++++++++++------------
 web_src/js/features/repo-legacy.js      |   2 +-
 web_src/js/features/repo-projects.js    |   7 +-
 7 files changed, 78 insertions(+), 73 deletions(-)

diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index b65fe56cf2..72039a6013 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -286,7 +286,7 @@ rules:
   jquery/no-class: [0]
   jquery/no-clone: [2]
   jquery/no-closest: [0]
-  jquery/no-css: [0]
+  jquery/no-css: [2]
   jquery/no-data: [0]
   jquery/no-deferred: [2]
   jquery/no-delegate: [2]
@@ -409,7 +409,7 @@ rules:
   no-jquery/no-constructor-attributes: [2]
   no-jquery/no-contains: [2]
   no-jquery/no-context-prop: [2]
-  no-jquery/no-css: [0]
+  no-jquery/no-css: [2]
   no-jquery/no-data: [0]
   no-jquery/no-deferred: [2]
   no-jquery/no-delegate: [2]
diff --git a/web_src/js/features/colorpicker.js b/web_src/js/features/colorpicker.js
index a5fdb3f5a6..df0353376d 100644
--- a/web_src/js/features/colorpicker.js
+++ b/web_src/js/features/colorpicker.js
@@ -1,10 +1,12 @@
-export async function createColorPicker($els) {
-  if (!$els || !$els.length) return;
+import $ from 'jquery';
+
+export async function createColorPicker(els) {
+  if (!els.length) return;
 
   await Promise.all([
     import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors'),
     import(/* webpackChunkName: "minicolors" */'@claviska/jquery-minicolors/jquery.minicolors.css'),
   ]);
 
-  $els.minicolors();
+  return $(els).minicolors();
 }
diff --git a/web_src/js/features/comp/ColorPicker.js b/web_src/js/features/comp/ColorPicker.js
index 5665b7a24a..d7e7038803 100644
--- a/web_src/js/features/comp/ColorPicker.js
+++ b/web_src/js/features/comp/ColorPicker.js
@@ -2,11 +2,15 @@ import $ from 'jquery';
 import {createColorPicker} from '../colorpicker.js';
 
 export function initCompColorPicker() {
-  createColorPicker($('.color-picker'));
+  (async () => {
+    await createColorPicker(document.querySelectorAll('.color-picker'));
 
-  $('.precolors .color').on('click', function () {
-    const color_hex = $(this).data('color-hex');
-    $('.color-picker').val(color_hex);
-    $('.minicolors-swatch-color').css('background-color', color_hex);
-  });
+    for (const el of document.querySelectorAll('.precolors .color')) {
+      el.addEventListener('click', (e) => {
+        const color = e.target.getAttribute('data-color-hex');
+        const parent = e.target.closest('.color.picker');
+        $(parent.querySelector('.color-picker')).minicolors('value', color);
+      });
+    }
+  })();
 }
diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js
index 26800ae05c..44fc9d9b6b 100644
--- a/web_src/js/features/comp/LabelEdit.js
+++ b/web_src/js/features/comp/LabelEdit.js
@@ -43,7 +43,6 @@ export function initCompLabelEdit(selector) {
 
   // Edit label
   $('.edit-label-button').on('click', function () {
-    $('.edit-label .color-picker').minicolors('value', $(this).data('color'));
     $('#label-modal-id').val($(this).data('id'));
 
     const $nameInput = $('.edit-label .label-name-input');
@@ -60,9 +59,8 @@ export function initCompLabelEdit(selector) {
       (!this.hasAttribute('data-exclusive') || !isExclusiveScopeName($nameInput.val())));
     updateExclusiveLabelEdit('.edit-label');
 
-    $('.edit-label .label-desc-input').val($(this).data('description'));
-    $('.edit-label .color-picker').val($(this).data('color'));
-    $('.edit-label .minicolors-swatch-color').css('background-color', $(this).data('color'));
+    $('.edit-label .label-desc-input').val(this.getAttribute('data-description'));
+    $('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color'));
 
     $('.edit-label.modal').modal({
       onApprove() {
diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js
index 80b7e83385..293e1f809a 100644
--- a/web_src/js/features/imagediff.js
+++ b/web_src/js/features/imagediff.js
@@ -133,24 +133,25 @@ export function initImageDiff() {
         $container.find('.bounds-info-before .bounds-info-height').text(`${sizes.image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : '');
       }
 
-      sizes.image1.css({
-        width: sizes.size1.width * factor,
-        height: sizes.size1.height * factor
-      });
-      sizes.image1.parent().css({
-        margin: `10px auto`,
-        width: sizes.size1.width * factor + 2,
-        height: sizes.size1.height * factor + 2
-      });
-      sizes.image2.css({
-        width: sizes.size2.width * factor,
-        height: sizes.size2.height * factor
-      });
-      sizes.image2.parent().css({
-        margin: `10px auto`,
-        width: sizes.size2.width * factor + 2,
-        height: sizes.size2.height * factor + 2
-      });
+      const image1 = sizes.image1[0];
+      if (image1) {
+        const container = image1.parentNode;
+        image1.style.width = `${sizes.size1.width * factor}px`;
+        image1.style.height = `${sizes.size1.height * factor}px`;
+        container.style.margin = '10px auto';
+        container.style.width = `${sizes.size1.width * factor + 2}px`;
+        container.style.height = `${sizes.size1.height * factor + 2}px`;
+      }
+
+      const image2 = sizes.image2[0];
+      if (image2) {
+        const container = image2.parentNode;
+        image2.style.width = `${sizes.size2.width * factor}px`;
+        image2.style.height = `${sizes.size2.height * factor}px`;
+        container.style.margin = '10px auto';
+        container.style.width = `${sizes.size2.width * factor + 2}px`;
+        container.style.height = `${sizes.size2.height * factor + 2}px`;
+      }
     }
 
     function initSwipe(sizes) {
@@ -159,36 +160,39 @@ export function initImageDiff() {
         factor = (diffContainerWidth - 12) / sizes.max.width;
       }
 
-      sizes.image1.css({
-        width: sizes.size1.width * factor,
-        height: sizes.size1.height * factor
-      });
-      sizes.image1.parent().css({
-        margin: `0px ${sizes.ratio[0] * factor}px`,
-        width: sizes.size1.width * factor + 2,
-        height: sizes.size1.height * factor + 2
-      });
-      sizes.image1.parent().parent().css({
-        padding: `${sizes.ratio[1] * factor}px 0 0 0`,
-        width: sizes.max.width * factor + 2
-      });
-      sizes.image2.css({
-        width: sizes.size2.width * factor,
-        height: sizes.size2.height * factor
-      });
-      sizes.image2.parent().css({
-        margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`,
-        width: sizes.size2.width * factor + 2,
-        height: sizes.size2.height * factor + 2
-      });
-      sizes.image2.parent().parent().css({
-        width: sizes.max.width * factor + 2,
-        height: sizes.max.height * factor + 2
-      });
-      $container.find('.diff-swipe').css({
-        width: sizes.max.width * factor + 2,
-        height: sizes.max.height * factor + 30 /* extra height for inner "position: absolute" elements */,
-      });
+      const image1 = sizes.image1[0];
+      if (image1) {
+        const container = image1.parentNode;
+        const swipeFrame = container.parentNode;
+        image1.style.width = `${sizes.size1.width * factor}px`;
+        image1.style.height = `${sizes.size1.height * factor}px`;
+        container.style.margin = `0px ${sizes.ratio[0] * factor}px`;
+        container.style.width = `${sizes.size1.width * factor + 2}px`;
+        container.style.height = `${sizes.size1.height * factor + 2}px`;
+        swipeFrame.style.padding = `${sizes.ratio[1] * factor}px 0 0 0`;
+        swipeFrame.style.width = `${sizes.max.width * factor + 2}px`;
+      }
+
+      const image2 = sizes.image2[0];
+      if (image2) {
+        const container = image2.parentNode;
+        const swipeFrame = container.parentNode;
+        image2.style.width = `${sizes.size2.width * factor}px`;
+        image2.style.height = `${sizes.size2.height * factor}px`;
+        container.style.margin = `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`;
+        container.style.width = `${sizes.size2.width * factor + 2}px`;
+        container.style.height = `${sizes.size2.height * factor + 2}px`;
+        swipeFrame.style.width = `${sizes.max.width * factor + 2}px`;
+        swipeFrame.style.height = `${sizes.max.height * factor + 2}px`;
+      }
+
+      // extra height for inner "position: absolute" elements
+      const swipe = $container.find('.diff-swipe')[0];
+      if (swipe) {
+        swipe.style.width = `${sizes.max.width * factor + 2}px`;
+        swipe.style.height = `${sizes.max.height * factor + 30}px`;
+      }
+
       $container.find('.swipe-bar').on('mousedown', function(e) {
         e.preventDefault();
 
@@ -200,13 +204,9 @@ export function initImageDiff() {
           e2.preventDefault();
 
           const value = Math.max(0, Math.min(e2.clientX - $swipeFrame.offset().left, width));
+          $swipeBar[0].style.left = `${value}px`;
+          $container.find('.swipe-container')[0].style.width = `${$swipeFrame.width() - value}px`;
 
-          $swipeBar.css({
-            left: value
-          });
-          $container.find('.swipe-container').css({
-            width: $swipeFrame.width() - value
-          });
           $(document).on('mouseup.diff-swipe', () => {
             $(document).off('.diff-swipe');
           });
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index 6155b3a5da..11bb758187 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -389,7 +389,7 @@ async function onEditContent(event) {
               dz.emit('complete', attachment);
               dz.files.push(attachment);
               fileUuidDict[attachment.uuid] = {submitted: true};
-              $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%');
+              $dropzone.find(`img[src='${imgSrc}']`)[0].style.maxWidth = '100%';
               const $input = $(`<input id="${attachment.uuid}" name="files" type="hidden">`).val(attachment.uuid);
               $dropzone.find('.files').append($input);
             }
diff --git a/web_src/js/features/repo-projects.js b/web_src/js/features/repo-projects.js
index 1f86711ab1..e95d875ec4 100644
--- a/web_src/js/features/repo-projects.js
+++ b/web_src/js/features/repo-projects.js
@@ -72,7 +72,7 @@ async function initRepoProjectSortable() {
             await PUT($(column).data('url'), {
               data: {
                 sorting: i,
-                color: rgbToHex($(column).css('backgroundColor')),
+                color: rgbToHex(window.getComputedStyle($(column)[0]).backgroundColor),
               },
             });
           } catch (error) {
@@ -111,8 +111,9 @@ export function initRepoProject() {
     const $projectColorInput = $(this).find('#new_project_column_color');
     const $boardColumn = $(this).closest('.project-column');
 
-    if ($boardColumn.css('backgroundColor')) {
-      setLabelColor($projectHeader, rgbToHex($boardColumn.css('backgroundColor')));
+    const bgColor = $boardColumn[0].style.backgroundColor;
+    if (bgColor) {
+      setLabelColor($projectHeader, rgbToHex(bgColor));
     }
 
     $(this).find('.edit-project-column-button').on('click', async function (e) {