loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Replace coloris with vanilla-colorful (#30201)

Found [a better color
picker](https://github.com/web-padawan/vanilla-colorful) that [does not
rely](https://github.com/mdbassit/Coloris/issues/139) on
`querySelectorAll` or a global shared instance, and is also around a
third of the size of the previous one.

The popover is handled by tippy.js for which I introduced a new "bare"
theme and it uses a new sibling-based mechanism which should prove
useful later to create tippy popovers via HTML only.

<img width="846" alt="Screenshot 2024-03-31 at 04 03 38"
src="https://github.com/go-gitea/gitea/assets/115237/7639b911-a2d7-4f5c-bffd-a9d84561e747">

(cherry picked from commit 1195be41a13d2198ab644c8558549edd74485510)

authored by

silverwind and committed by
Gergely Nagy
a53a94e1 2adc3a45

+92 -162
+6 -6
package-lock.json
··· 13 13 "@github/relative-time-element": "4.4.0", 14 14 "@github/text-expander-element": "2.6.1", 15 15 "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", 16 - "@melloware/coloris": "0.23.0", 17 16 "@primer/octicons": "19.9.0", 18 17 "add-asset-webpack-plugin": "2.0.1", 19 18 "ansi_up": "6.0.2", ··· 54 53 "toastify-js": "1.12.0", 55 54 "tributejs": "5.1.3", 56 55 "uint8-to-base64": "0.2.0", 56 + "vanilla-colorful": "0.7.2", 57 57 "vue": "3.4.21", 58 58 "vue-bar-graph": "2.0.0", 59 59 "vue-chartjs": "5.3.0", ··· 1290 1290 "dependencies": { 1291 1291 "@mcaptcha/core-glue": "^0.1.0-alpha-5" 1292 1292 } 1293 - }, 1294 - "node_modules/@melloware/coloris": { 1295 - "version": "0.23.0", 1296 - "resolved": "https://registry.npmjs.org/@melloware/coloris/-/coloris-0.23.0.tgz", 1297 - "integrity": "sha512-VGIjI9+IQwg6BHjIE10yl0K2ARYz5bsjn6BgFEs1y1ErPAQymgdoxwVcSVL4Ai5t9OVs8xaCB7JKHqFu2N96Ow==" 1298 1293 }, 1299 1294 "node_modules/@nodelib/fs.scandir": { 1300 1295 "version": "2.1.5", ··· 12009 12004 "dependencies": { 12010 12005 "builtins": "^1.0.3" 12011 12006 } 12007 + }, 12008 + "node_modules/vanilla-colorful": { 12009 + "version": "0.7.2", 12010 + "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", 12011 + "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==" 12012 12012 }, 12013 12013 "node_modules/vite": { 12014 12014 "version": "5.2.6",
+1 -1
package.json
··· 12 12 "@github/relative-time-element": "4.4.0", 13 13 "@github/text-expander-element": "2.6.1", 14 14 "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", 15 - "@melloware/coloris": "0.23.0", 16 15 "@primer/octicons": "19.9.0", 17 16 "add-asset-webpack-plugin": "2.0.1", 18 17 "ansi_up": "6.0.2", ··· 53 52 "toastify-js": "1.12.0", 54 53 "tributejs": "5.1.3", 55 54 "uint8-to-base64": "0.2.0", 55 + "vanilla-colorful": "0.7.2", 56 56 "vue": "3.4.21", 57 57 "vue-bar-graph": "2.0.0", 58 58 "vue-chartjs": "5.3.0",
+12 -129
web_src/css/features/colorpicker.css
··· 1 - /* This is a stripped-down version of coloris's CSS tailored to our needs. It does only include 2 - opaqua colors, and if more features like opacity are needed, the CSS needs to be extended 3 - based on upstream: https://github.com/mdbassit/Coloris/blob/main/src/coloris.css. */ 4 - 5 1 .js-color-picker-input { 6 2 display: flex; 7 - flex-wrap: wrap; 3 + position: relative; 8 4 } 9 5 10 6 .js-color-picker-input input { ··· 13 9 padding-left: 32px !important; 14 10 } 15 11 16 - .clr-picker { 17 - display: none; 18 - flex-wrap: wrap; 19 - position: absolute; 20 - width: 200px; 21 - z-index: 1002; /* above .ui.modal which has 1001 */ 22 - border-radius: var(--border-radius); 23 - background-color: var(--color-menu); 24 - justify-content: flex-end; 25 - direction: ltr; 26 - box-shadow: 0 5px 20px var(--color-shadow); 27 - user-select: none; 28 - } 29 - 30 - .clr-picker.clr-open { 31 - display: flex; 32 - } 33 - 34 - .clr-gradient { 35 - position: relative; 36 - width: 100%; 37 - height: 100px; 38 - border-radius: 3px 3px 0 0; 39 - background: linear-gradient(rgba(0,0,0,0), #000), linear-gradient(90deg, #fff, currentcolor); /* stylelint-disable-line scale-unlimited/declaration-strict-value */ 40 - cursor: pointer; 41 - } 42 - 43 - .clr-marker { 44 - position: absolute; 45 - width: 12px; 46 - height: 12px; 47 - margin: -6px 0 0 -6px; 48 - border: 1px solid var(--color-white); 49 - border-radius: 50%; 50 - background-color: currentcolor; 51 - cursor: pointer; 52 - } 53 - 54 - .clr-picker input[type="range"]::-webkit-slider-runnable-track { 55 - width: 100%; 56 - height: 16px; 57 - } 58 - 59 - .clr-picker input[type="range"]::-webkit-slider-thumb { 60 - width: 16px; 61 - height: 16px; 62 - -webkit-appearance: none; 63 - } 64 - 65 - .clr-picker input[type="range"]::-moz-range-track { 66 - width: 100%; 67 - height: 16px; 68 - border: 0; 69 - } 70 - 71 - .clr-picker input[type="range"]::-moz-range-thumb { 72 - width: 16px; 73 - height: 16px; 74 - border: 0; 75 - } 76 - 77 - .clr-hue { 78 - 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 */ 79 - position: relative; 80 - width: calc(100% - 40px); 81 - height: 10px; 82 - margin: 10px 20px; 83 - border-radius: 4px; 84 - } 85 - 86 - .clr-hue input[type="range"] { 87 - position: absolute; 88 - width: calc(100% + 32px); 89 - margin: 0; 90 - background-color: transparent; 91 - opacity: 0; 92 - cursor: pointer; 93 - appearance: none; 94 - } 95 - 96 - .clr-hue div { 97 - position: absolute; 98 - width: 16px; 99 - height: 16px; 100 - left: 0; 101 - top: 50%; 102 - transform: translate(-50%, -50%); 103 - border: 2px solid var(--color-white); 104 - border-radius: 50%; 105 - background-color: currentcolor; 106 - box-shadow: 0 0 1px var(--color-shadow); 107 - pointer-events: none; 108 - } 109 - 110 - .clr-field { 111 - flex: 1; 112 - position: relative; 113 - color: transparent; 114 - } 115 - 116 - .clr-field button { 12 + .js-color-picker-input .preview-square { 117 13 position: absolute; 118 14 aspect-ratio: 1; 119 15 height: 16px; 120 16 left: 10px; 121 17 top: 50%; 122 18 transform: translateY(-50%); 123 - margin: 0; 124 - padding: 0; 125 - border: 0; 126 - color: inherit; 127 - pointer-events: none; 128 19 border-radius: 2px; 129 20 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 */ 130 21 background-position: 0 0, 4px 4px; 131 22 background-size: 8px 8px; 132 23 } 133 24 134 - .clr-field button::after { 25 + .js-color-picker-input .preview-square::after { 135 26 content: ""; 136 - display: block; 137 27 position: absolute; 138 28 width: 100%; 139 29 height: 100%; 140 - left: 0; 141 - top: 0; 142 30 border-radius: inherit; 143 31 background-color: currentcolor; 144 32 } 145 33 146 - .clr-marker:focus { 147 - outline: none; 34 + hex-color-picker { 35 + width: 180px; 36 + height: 120px; 148 37 } 149 38 150 - .clr-keyboard-nav .clr-marker:focus, 151 - .clr-keyboard-nav .clr-hue input:focus + div, 152 - .clr-keyboard-nav .clr-alpha input:focus + div { 153 - outline: none; 154 - box-shadow: 0 0 2px 2px var(--color-white); 39 + hex-color-picker::part(hue-pointer), 40 + hex-color-picker::part(saturation-pointer) { 41 + width: 22px; 42 + height: 22px; 155 43 } 156 44 157 - .clr-picker .clr-preview, 158 - .clr-picker .clr-clear, 159 - .clr-picker .clr-swatches, 160 - .clr-picker .clr-format, 161 - .clr-picker .clr-alpha, 162 - .clr-picker .clr-color { 163 - display: none; 45 + hex-color-picker::part(hue) { 46 + flex-basis: 16px; 164 47 }
+11
web_src/css/modules/tippy.css
··· 29 29 z-index: 1; 30 30 } 31 31 32 + /* bare theme, no styling at all, except box-shadow */ 33 + .tippy-box[data-theme="bare"] { 34 + border: none; 35 + box-shadow: 0 6px 18px var(--color-shadow); 36 + } 37 + 38 + .tippy-box[data-theme="bare"] .tippy-content { 39 + padding: 0; 40 + background: transparent; 41 + } 42 + 32 43 /* tooltip theme for text tooltips */ 33 44 34 45 .tippy-box[data-theme="tooltip"] {
+58 -23
web_src/js/features/colorpicker.js
··· 1 - export async function initColorPickers(selector = '.js-color-picker-input input', opts = {}) { 2 - const inputEls = document.querySelectorAll(selector); 3 - if (!inputEls.length) return; 1 + import {createTippy} from '../modules/tippy.js'; 2 + 3 + export async function initColorPickers() { 4 + const els = document.getElementsByClassName('js-color-picker-input'); 5 + if (!els.length) return; 4 6 5 - const [{coloris, init}] = await Promise.all([ 6 - import(/* webpackChunkName: "colorpicker" */'@melloware/coloris'), 7 + await Promise.all([ 8 + import(/* webpackChunkName: "colorpicker" */'vanilla-colorful/hex-color-picker.js'), 7 9 import(/* webpackChunkName: "colorpicker" */'../../css/features/colorpicker.css'), 8 10 ]); 9 11 10 - init(); 11 - coloris({ 12 - el: selector, 13 - alpha: false, 14 - focusInput: true, 15 - selectInput: false, 16 - ...opts, 12 + for (const el of els) { 13 + initPicker(el); 14 + } 15 + } 16 + 17 + function updateSquare(el, newValue) { 18 + el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent'; 19 + } 20 + 21 + function updatePicker(el, newValue) { 22 + el.setAttribute('color', newValue); 23 + } 24 + 25 + function initPicker(el) { 26 + const input = el.querySelector('input'); 27 + 28 + const square = document.createElement('div'); 29 + square.classList.add('preview-square'); 30 + updateSquare(square, input.value); 31 + el.append(square); 32 + 33 + const picker = document.createElement('hex-color-picker'); 34 + picker.addEventListener('color-changed', (e) => { 35 + input.value = e.detail.value; 36 + input.focus(); 37 + updateSquare(square, e.detail.value); 38 + }); 39 + 40 + input.addEventListener('input', (e) => { 41 + updateSquare(square, e.target.value); 42 + updatePicker(picker, e.target.value); 17 43 }); 18 44 19 - for (const inputEl of inputEls) { 20 - const parent = inputEl.closest('.js-color-picker-input'); 21 - // prevent tabbing on the color preview `button` inside the input 22 - parent.querySelector('button').tabIndex = -1; 23 - // init precolors 24 - for (const el of parent.querySelectorAll('.precolors .color')) { 25 - el.addEventListener('click', (e) => { 26 - inputEl.value = e.target.getAttribute('data-color-hex'); 27 - inputEl.dispatchEvent(new Event('input', {bubbles: true})); 28 - }); 29 - } 45 + createTippy(input, { 46 + trigger: 'focus click', 47 + theme: 'bare', 48 + hideOnClick: true, 49 + content: picker, 50 + placement: 'bottom-start', 51 + interactive: true, 52 + onShow() { 53 + updatePicker(picker, input.value); 54 + }, 55 + }); 56 + 57 + // init precolors 58 + for (const colorEl of el.querySelectorAll('.precolors .color')) { 59 + colorEl.addEventListener('click', (e) => { 60 + const newValue = e.target.getAttribute('data-color-hex'); 61 + input.value = newValue; 62 + input.dispatchEvent(new Event('input', {bubbles: true})); 63 + updateSquare(square, newValue); 64 + }); 30 65 } 31 66 }
+4 -3
web_src/js/modules/tippy.js
··· 3 3 import {formatDatetime} from '../utils/time.js'; 4 4 5 5 const visibleInstances = new Set(); 6 + const arrowSvg = `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`; 6 7 7 8 export function createTippy(target, opts = {}) { 8 9 // the callback functions should be destructured from opts, 9 10 // because we should use our own wrapper functions to handle them, do not let the user override them 10 - const {onHide, onShow, onDestroy, role, theme, ...other} = opts; 11 + const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; 11 12 12 13 const instance = tippy(target, { 13 14 appendTo: document.body, ··· 35 36 visibleInstances.add(instance); 36 37 return onShow?.(instance); 37 38 }, 38 - arrow: `<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`, 39 + arrow: arrow || (theme === 'bare' ? false : arrowSvg), 39 40 role: role || 'menu', // HTML role attribute 40 - theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu" or "box-with-header" 41 + theme: theme || role || 'menu', // CSS theme, either "tooltip", "menu", "box-with-header" or "bare" 41 42 plugins: [followCursor], 42 43 ...other, 43 44 });