From 2e542f17a3e09b662d6915bd23fbd014f65001e6 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 29 Mar 2024 04:00:07 +0100 Subject: [PATCH] replace jquery-minicolors with coloris (#30055) Get rid of one more jQuery dependant and have a nicer color picker as well. Now there is only a single global color picker init because that is all that's necessary because the elements are present on the page when the init code runs. The init is slightly weird because the module only takes a selector instead of DOM elements directly. The label modals now also perform form validation because previously it was possible to trigger a 500 error `Color cannot be empty.` by clearing out the color value on labels. Screenshot 2024-03-25 at 00 21 05 Screenshot 2024-03-25 at 00 20 48 (cherry picked from commit dd8dde2be89921b2b1497c6cc5eafdde213429cb) --- .dockerignore | 1 - .gitignore | 1 - Makefile | 2 +- package-lock.json | 15 +- package.json | 2 +- templates/projects/view.tmpl | 8 +- .../repo/issue/labels/edit_delete_label.tmpl | 4 +- templates/repo/issue/labels/label_new.tmpl | 4 +- web_src/css/base.css | 5 - web_src/css/features/colorpicker.css | 164 ++++++++++++++++++ web_src/css/features/projects.css | 23 --- web_src/css/repo.css | 18 -- web_src/js/features/colorpicker.js | 35 +++- web_src/js/features/common-global.js | 6 +- web_src/js/features/comp/ColorPicker.js | 16 -- web_src/js/features/comp/LabelEdit.js | 17 +- web_src/js/index.js | 2 + webpack.config.js | 7 - 18 files changed, 224 insertions(+), 106 deletions(-) create mode 100644 web_src/css/features/colorpicker.css delete mode 100644 web_src/js/features/comp/ColorPicker.js diff --git a/.dockerignore b/.dockerignore index 4c14a9462..86cc8f608 100644 --- a/.dockerignore +++ b/.dockerignore @@ -77,7 +77,6 @@ cpu.out /public/assets/css /public/assets/fonts /public/assets/img/avatar -/public/assets/img/webpack /vendor /web_src/fomantic/node_modules /web_src/fomantic/build/* diff --git a/.gitignore b/.gitignore index b883e079d..be8db3b51 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,6 @@ cpu.out /public/assets/css /public/assets/fonts /public/assets/licenses.txt -/public/assets/img/webpack /vendor /web_src/fomantic/node_modules /web_src/fomantic/build/* diff --git a/Makefile b/Makefile index fec96de98..b66a998b5 100644 --- a/Makefile +++ b/Makefile @@ -130,7 +130,7 @@ FOMANTIC_WORK_DIR := web_src/fomantic WEBPACK_SOURCES := $(shell find web_src/js web_src/css -type f) WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css -WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts public/assets/img/webpack +WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST)) diff --git a/package-lock.json b/package-lock.json index 72b00444b..47e4c6cf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "@citation-js/plugin-bibtex": "0.7.9", "@citation-js/plugin-csl": "0.7.9", "@citation-js/plugin-software-formats": "0.6.1", - "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.3", "@github/relative-time-element": "4.4.0", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", + "@melloware/coloris": "0.23.0", "@primer/octicons": "19.9.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "6.0.2", @@ -395,14 +395,6 @@ "node": ">=14.0.0" } }, - "node_modules/@claviska/jquery-minicolors": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@claviska/jquery-minicolors/-/jquery-minicolors-2.3.6.tgz", - "integrity": "sha512-8Ro6D4GCrmOl41+6w4NFhEOpx8vjxwVRI69bulXsFDt49uVRKhLU5TnzEV7AmOJrylkVq+ugnYNMiGHBieeKUQ==", - "peerDependencies": { - "jquery": ">= 1.7.x" - } - }, "node_modules/@csstools/css-parser-algorithms": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.1.tgz", @@ -1298,6 +1290,11 @@ "@mcaptcha/core-glue": "^0.1.0-alpha-5" } }, + "node_modules/@melloware/coloris": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@melloware/coloris/-/coloris-0.23.0.tgz", + "integrity": "sha512-VGIjI9+IQwg6BHjIE10yl0K2ARYz5bsjn6BgFEs1y1ErPAQymgdoxwVcSVL4Ai5t9OVs8xaCB7JKHqFu2N96Ow==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index 779eb36ae..f8618515f 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "@citation-js/plugin-bibtex": "0.7.9", "@citation-js/plugin-csl": "0.7.9", "@citation-js/plugin-software-formats": "0.6.1", - "@claviska/jquery-minicolors": "2.3.6", "@github/markdown-toolbar-element": "2.2.3", "@github/relative-time-element": "4.4.0", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", + "@melloware/coloris": "0.23.0", "@primer/octicons": "19.9.0", "add-asset-webpack-plugin": "2.0.1", "ansi_up": "6.0.2", diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index b45174b08..33dd758c7 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -42,8 +42,8 @@
-
- +
+ {{template "repo/issue/label_precolors"}}
@@ -114,8 +114,8 @@
-
- +
+ {{template "repo/issue/label_precolors"}}
diff --git a/templates/repo/issue/labels/edit_delete_label.tmpl b/templates/repo/issue/labels/edit_delete_label.tmpl index 98e0f4702..fcf69217e 100644 --- a/templates/repo/issue/labels/edit_delete_label.tmpl +++ b/templates/repo/issue/labels/edit_delete_label.tmpl @@ -52,8 +52,8 @@
-
- +
+ {{template "repo/issue/label_precolors"}}
diff --git a/templates/repo/issue/labels/label_new.tmpl b/templates/repo/issue/labels/label_new.tmpl index 2b2b2336c..32fd8e76d 100644 --- a/templates/repo/issue/labels/label_new.tmpl +++ b/templates/repo/issue/labels/label_new.tmpl @@ -27,8 +27,8 @@
-
- +
+ {{template "repo/issue/label_precolors"}}
diff --git a/web_src/css/base.css b/web_src/css/base.css index dc34728df..de3dbb40e 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1457,11 +1457,6 @@ table th[data-sortt-desc] .svg { vertical-align: -0.15em; } -/* for the jquery.minicolors plugin */ -.minicolors-panel { - background: var(--color-secondary-dark-1) !important; -} - .ui.tabular.menu { border-color: var(--color-secondary); } diff --git a/web_src/css/features/colorpicker.css b/web_src/css/features/colorpicker.css new file mode 100644 index 000000000..0c651cfeb --- /dev/null +++ b/web_src/css/features/colorpicker.css @@ -0,0 +1,164 @@ +/* This is a stripped-down version of coloris's CSS tailored to our needs. It does only include + opaqua colors, and if more features like opacity are needed, the CSS needs to be extended + based on upstream: https://github.com/mdbassit/Coloris/blob/main/src/coloris.css. */ + +.js-color-picker-input { + display: flex; + flex-wrap: wrap; +} + +.js-color-picker-input input { + padding-top: 8px !important; + padding-bottom: 8px !important; + padding-left: 32px !important; +} + +.clr-picker { + display: none; + flex-wrap: wrap; + position: absolute; + width: 200px; + z-index: 1002; /* above .ui.modal which has 1001 */ + border-radius: var(--border-radius); + background-color: var(--color-menu); + justify-content: flex-end; + direction: ltr; + box-shadow: 0 5px 20px var(--color-shadow); + user-select: none; +} + +.clr-picker.clr-open { + display: flex; +} + +.clr-gradient { + position: relative; + width: 100%; + height: 100px; + border-radius: 3px 3px 0 0; + background: linear-gradient(rgba(0,0,0,0), #000), linear-gradient(90deg, #fff, currentcolor); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ + cursor: pointer; +} + +.clr-marker { + position: absolute; + width: 12px; + height: 12px; + margin: -6px 0 0 -6px; + border: 1px solid var(--color-white); + border-radius: 50%; + background-color: currentcolor; + cursor: pointer; +} + +.clr-picker input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 16px; +} + +.clr-picker input[type="range"]::-webkit-slider-thumb { + width: 16px; + height: 16px; + -webkit-appearance: none; +} + +.clr-picker input[type="range"]::-moz-range-track { + width: 100%; + height: 16px; + border: 0; +} + +.clr-picker input[type="range"]::-moz-range-thumb { + width: 16px; + height: 16px; + border: 0; +} + +.clr-hue { + background: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ + position: relative; + width: calc(100% - 40px); + height: 10px; + margin: 10px 20px; + border-radius: 4px; +} + +.clr-hue input[type="range"] { + position: absolute; + width: calc(100% + 32px); + margin: 0; + background-color: transparent; + opacity: 0; + cursor: pointer; + appearance: none; +} + +.clr-hue div { + position: absolute; + width: 16px; + height: 16px; + left: 0; + top: 50%; + transform: translate(-50%, -50%); + border: 2px solid var(--color-white); + border-radius: 50%; + background-color: currentcolor; + box-shadow: 0 0 1px var(--color-shadow); + pointer-events: none; +} + +.clr-field { + flex: 1; + position: relative; + color: transparent; +} + +.clr-field button { + position: absolute; + aspect-ratio: 1; + height: 16px; + left: 10px; + top: 50%; + transform: translateY(-50%); + margin: 0; + padding: 0; + border: 0; + color: inherit; + pointer-events: none; + border-radius: 2px; + background: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ + background-position: 0 0, 4px 4px; + background-size: 8px 8px; +} + +.clr-field button::after { + content: ""; + display: block; + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + border-radius: inherit; + background-color: currentcolor; +} + +.clr-marker:focus { + outline: none; +} + +.clr-keyboard-nav .clr-marker:focus, +.clr-keyboard-nav .clr-hue input:focus + div, +.clr-keyboard-nav .clr-alpha input:focus + div { + outline: none; + box-shadow: 0 0 2px 2px var(--color-white); +} + +.clr-picker .clr-preview, +.clr-picker .clr-clear, +.clr-picker .clr-swatches, +.clr-picker .clr-format, +.clr-picker .clr-alpha, +.clr-picker .clr-color { + display: none; +} diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index 30df994c3..cec5e6fc6 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -102,26 +102,3 @@ .card-ghost * { opacity: 0; } - -.color-field .minicolors.minicolors-theme-default { - display: block; -} - -.color-field .minicolors.minicolors-theme-default .minicolors-input { - height: 38px; - padding-left: 2rem; -} - -.color-field .minicolors.minicolors-theme-default .minicolors-swatch { - top: 10px; -} - -.edit-project-column-modal .color.picker.column, -.new-project-column-modal .color.picker.column { - display: flex; -} - -.edit-project-column-modal .color.picker.column .minicolors, -.new-project-column-modal .color.picker.column .minicolors { - flex: 1; -} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 28e78730d..35d69c3ef 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -2299,24 +2299,6 @@ padding-top: 15px; } -.edit-label.modal .form .color.picker.column, -.new-label.modal .form .color.picker.column { - display: flex; -} - -.edit-label.modal .form .color.picker.column .minicolors, -.new-label.modal .form .color.picker.column .minicolors { - flex: 1; -} - -.edit-label.modal .form .minicolors-swatch.minicolors-sprite, -.new-label.modal .form .minicolors-swatch.minicolors-sprite { - top: 10px; - left: 10px; - width: 15px; - height: 15px; -} - .tab-size-1 { tab-size: 1 !important; -moz-tab-size: 1 !important; diff --git a/web_src/js/features/colorpicker.js b/web_src/js/features/colorpicker.js index df0353376..f342598e6 100644 --- a/web_src/js/features/colorpicker.js +++ b/web_src/js/features/colorpicker.js @@ -1,12 +1,31 @@ -import $ from 'jquery'; +export async function initColorPickers(selector = '.js-color-picker-input input', opts = {}) { + const inputEls = document.querySelectorAll(selector); + if (!inputEls.length) return; -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'), + const [{coloris, init}] = await Promise.all([ + import(/* webpackChunkName: "colorpicker" */'@melloware/coloris'), + import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'), ]); - return $(els).minicolors(); + init(); + coloris({ + el: selector, + alpha: false, + focusInput: true, + selectInput: false, + ...opts, + }); + + for (const inputEl of inputEls) { + const parent = inputEl.closest('.js-color-picker-input'); + // prevent tabbing on the color preview `button` inside the input + parent.querySelector('button').tabIndex = -1; + // init precolors + for (const el of parent.querySelectorAll('.precolors .color')) { + el.addEventListener('click', (e) => { + inputEl.value = e.target.getAttribute('data-color-hex'); + inputEl.dispatchEvent(new Event('input', {bubbles: true})); + }); + } + } } diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index 18849ba7c..ce702f041 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -2,7 +2,6 @@ import $ from 'jquery'; import '../vendor/jquery.are-you-sure.js'; import {clippie} from 'clippie'; import {createDropzone} from './dropzone.js'; -import {initCompColorPicker} from './comp/ColorPicker.js'; import {showGlobalErrorMessage} from '../bootstrap.js'; import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js'; import {svg} from '../svg.js'; @@ -379,10 +378,7 @@ function initGlobalShowModal() { $attrTarget.text(attrib.value); // FIXME: it should be more strict here, only handle div/span/p } } - const $colorPickers = $modal.find('.color-picker'); - if ($colorPickers.length > 0) { - initCompColorPicker(); // FIXME: this might cause duplicate init - } + $modal.modal('setting', { onApprove: () => { // "form-fetch-action" can handle network errors gracefully, diff --git a/web_src/js/features/comp/ColorPicker.js b/web_src/js/features/comp/ColorPicker.js deleted file mode 100644 index d7e703880..000000000 --- a/web_src/js/features/comp/ColorPicker.js +++ /dev/null @@ -1,16 +0,0 @@ -import $ from 'jquery'; -import {createColorPicker} from '../colorpicker.js'; - -export function initCompColorPicker() { - (async () => { - await createColorPicker(document.querySelectorAll('.color-picker')); - - 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 843657a6b..2cc75cc6b 100644 --- a/web_src/js/features/comp/LabelEdit.js +++ b/web_src/js/features/comp/LabelEdit.js @@ -1,5 +1,4 @@ import $ from 'jquery'; -import {initCompColorPicker} from './ColorPicker.js'; function isExclusiveScopeName(name) { return /.*[^/]\/[^/].*/.test(name); @@ -28,13 +27,17 @@ function updateExclusiveLabelEdit(form) { export function initCompLabelEdit(selector) { if (!$(selector).length) return; - initCompColorPicker(); // Create label $('.new-label.button').on('click', () => { updateExclusiveLabelEdit('.new-label'); $('.new-label.modal').modal({ onApprove() { + const form = document.querySelector('.new-label.form'); + if (!form.checkValidity()) { + form.reportValidity(); + return false; + } $('.new-label.form').trigger('submit'); }, }).modal('show'); @@ -60,10 +63,18 @@ export function initCompLabelEdit(selector) { updateExclusiveLabelEdit('.edit-label'); $('.edit-label .label-desc-input').val(this.getAttribute('data-description')); - $('.edit-label .color-picker').minicolors('value', this.getAttribute('data-color')); + + const colorInput = document.querySelector('.edit-label .js-color-picker-input input'); + colorInput.value = this.getAttribute('data-color'); + colorInput.dispatchEvent(new Event('input', {bubbles: true})); $('.edit-label.modal').modal({ onApprove() { + const form = document.querySelector('.edit-label.form'); + if (!form.checkValidity()) { + form.reportValidity(); + return false; + } $('.edit-label.form').trigger('submit'); }, }).modal('show'); diff --git a/web_src/js/index.js b/web_src/js/index.js index 4c707486b..fc2f6b9b0 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -86,6 +86,7 @@ import {initRepoRecentCommits} from './features/recent-commits.js'; import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'; import {initDirAuto} from './modules/dirauto.js'; import {initRepositorySearch} from './features/repo-search.js'; +import {initColorPickers} from './features/colorpicker.js'; // Init Gitea's Fomantic settings initGiteaFomantic(); @@ -188,4 +189,5 @@ onDomReady(() => { initRepoDiffView(); initPdfViewer(); initScopedAccessTokenCategories(); + initColorPickers(); }); diff --git a/webpack.config.js b/webpack.config.js index b6ecb2942..9e3bc6a20 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -196,13 +196,6 @@ export default { filename: 'fonts/[name].[contenthash:8][ext]', }, }, - { - test: /\.png$/i, - type: 'asset/resource', - generator: { - filename: 'img/webpack/[name].[contenthash:8][ext]', - }, - }, ], }, plugins: [