Backport https://github.com/go-gitea/gitea/pull/25780, clean cherry-pick. Monaco can not deal with color formats other than 6-digit hex, so we convert the colors for it via new [`tinycolor2`](https://github.com/bgrins/TinyColor) dependency (5kB minzipped). Also, with the addition of the module, we can replace the existing `hexToRGBColor` usage, I verified it is compatible with the current tests before removing the function. Fixes: https://github.com/go-gitea/gitea/issues/25770tags/v1.20.0
@@ -42,6 +42,7 @@ | |||
"sortablejs": "1.15.0", | |||
"swagger-ui-dist": "5.0.0", | |||
"throttle-debounce": "5.0.0", | |||
"tinycolor2": "1.6.0", | |||
"tippy.js": "6.3.7", | |||
"tributejs": "5.1.3", | |||
"uint8-to-base64": "0.2.0", | |||
@@ -10085,6 +10086,11 @@ | |||
"integrity": "sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==", | |||
"dev": true | |||
}, | |||
"node_modules/tinycolor2": { | |||
"version": "1.6.0", | |||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", | |||
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" | |||
}, | |||
"node_modules/tinypool": { | |||
"version": "0.5.0", | |||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz", |
@@ -42,6 +42,7 @@ | |||
"sortablejs": "1.15.0", | |||
"swagger-ui-dist": "5.0.0", | |||
"throttle-debounce": "5.0.0", | |||
"tinycolor2": "1.6.0", | |||
"tippy.js": "6.3.7", | |||
"tributejs": "5.1.3", | |||
"uint8-to-base64": "0.2.0", |
@@ -26,7 +26,8 @@ | |||
<script> | |||
import $ from 'jquery'; | |||
import {SvgIcon} from '../svg.js'; | |||
import {useLightTextOnBackground, hexToRGBColor} from '../utils/color.js'; | |||
import {useLightTextOnBackground} from '../utils/color.js'; | |||
import tinycolor from 'tinycolor2'; | |||
const {appSubUrl, i18n} = window.config; | |||
@@ -77,7 +78,7 @@ export default { | |||
labels() { | |||
return this.issue.labels.map((label) => { | |||
let textColor; | |||
const [r, g, b] = hexToRGBColor(label.color); | |||
const {r, g, b} = tinycolor(label.color).toRgb(); | |||
if (useLightTextOnBackground(r, g, b)) { | |||
textColor = '#eeeeee'; | |||
} else { |
@@ -1,3 +1,4 @@ | |||
import tinycolor from 'tinycolor2'; | |||
import {basename, extname, isObject, isDarkTheme} from '../utils.js'; | |||
import {onInputDebounce} from '../utils/dom.js'; | |||
@@ -69,33 +70,34 @@ export async function createMonaco(textarea, filename, editorOpts) { | |||
textarea.parentNode.append(container); | |||
// https://github.com/microsoft/monaco-editor/issues/2427 | |||
// also, monaco can only parse 6-digit hex colors, so we convert the colors to that format | |||
const styles = window.getComputedStyle(document.documentElement); | |||
const getProp = (name) => styles.getPropertyValue(name).trim(); | |||
const getColor = (name) => tinycolor(styles.getPropertyValue(name).trim()).toString('hex6'); | |||
monaco.editor.defineTheme('gitea', { | |||
base: isDarkTheme() ? 'vs-dark' : 'vs', | |||
inherit: true, | |||
rules: [ | |||
{ | |||
background: getProp('--color-code-bg'), | |||
background: getColor('--color-code-bg'), | |||
} | |||
], | |||
colors: { | |||
'editor.background': getProp('--color-code-bg'), | |||
'editor.foreground': getProp('--color-text'), | |||
'editor.inactiveSelectionBackground': getProp('--color-primary-light-4'), | |||
'editor.lineHighlightBackground': getProp('--color-editor-line-highlight'), | |||
'editor.selectionBackground': getProp('--color-primary-light-3'), | |||
'editor.selectionForeground': getProp('--color-primary-light-3'), | |||
'editorLineNumber.background': getProp('--color-code-bg'), | |||
'editorLineNumber.foreground': getProp('--color-secondary-dark-6'), | |||
'editorWidget.background': getProp('--color-body'), | |||
'editorWidget.border': getProp('--color-secondary'), | |||
'input.background': getProp('--color-input-background'), | |||
'input.border': getProp('--color-input-border'), | |||
'input.foreground': getProp('--color-input-text'), | |||
'scrollbar.shadow': getProp('--color-shadow'), | |||
'progressBar.background': getProp('--color-primary'), | |||
'editor.background': getColor('--color-code-bg'), | |||
'editor.foreground': getColor('--color-text'), | |||
'editor.inactiveSelectionBackground': getColor('--color-primary-light-4'), | |||
'editor.lineHighlightBackground': getColor('--color-editor-line-highlight'), | |||
'editor.selectionBackground': getColor('--color-primary-light-3'), | |||
'editor.selectionForeground': getColor('--color-primary-light-3'), | |||
'editorLineNumber.background': getColor('--color-code-bg'), | |||
'editorLineNumber.foreground': getColor('--color-secondary-dark-6'), | |||
'editorWidget.background': getColor('--color-body'), | |||
'editorWidget.border': getColor('--color-secondary'), | |||
'input.background': getColor('--color-input-background'), | |||
'input.border': getColor('--color-input-border'), | |||
'input.foreground': getColor('--color-input-text'), | |||
'scrollbar.shadow': getColor('--color-shadow'), | |||
'progressBar.background': getColor('--color-primary'), | |||
} | |||
}); | |||
@@ -1,5 +1,6 @@ | |||
import $ from 'jquery'; | |||
import {useLightTextOnBackground, hexToRGBColor} from '../utils/color.js'; | |||
import {useLightTextOnBackground} from '../utils/color.js'; | |||
import tinycolor from 'tinycolor2'; | |||
const {csrfToken} = window.config; | |||
@@ -190,7 +191,7 @@ export function initRepoProject() { | |||
} | |||
function setLabelColor(label, color) { | |||
const [r, g, b] = hexToRGBColor(color); | |||
const {r, g, b} = tinycolor(color).toRgb(); | |||
if (useLightTextOnBackground(r, g, b)) { | |||
label.removeClass('dark-label').addClass('light-label'); | |||
} else { |
@@ -13,27 +13,6 @@ function getLuminance(r, g, b) { | |||
return 0.2126 * R + 0.7152 * G + 0.0722 * B; | |||
} | |||
// Get color as RGB values in 0..255 range from the hex color string (with or without #) | |||
export function hexToRGBColor(backgroundColorStr) { | |||
let backgroundColor = backgroundColorStr; | |||
if (backgroundColorStr[0] === '#') { | |||
backgroundColor = backgroundColorStr.substring(1); | |||
} | |||
// only support transfer of rgb, rgba, rrggbb and rrggbbaa | |||
// if not in these formats, use default values 0, 0, 0 | |||
if (![3, 4, 6, 8].includes(backgroundColor.length)) { | |||
return [0, 0, 0]; | |||
} | |||
if ([3, 4].includes(backgroundColor.length)) { | |||
const [r, g, b] = backgroundColor; | |||
backgroundColor = `${r}${r}${g}${g}${b}${b}`; | |||
} | |||
const r = parseInt(backgroundColor.substring(0, 2), 16); | |||
const g = parseInt(backgroundColor.substring(2, 4), 16); | |||
const b = parseInt(backgroundColor.substring(4, 6), 16); | |||
return [r, g, b]; | |||
} | |||
// Reference from: https://firsching.ch/github_labels.html | |||
// In the future WCAG 3 APCA may be a better solution. | |||
// Check if text should use light color based on RGB of background |
@@ -1,17 +1,5 @@ | |||
import {test, expect} from 'vitest'; | |||
import {hexToRGBColor, useLightTextOnBackground} from './color.js'; | |||
test('hexToRGBColor', () => { | |||
expect(hexToRGBColor('2b8685')).toEqual([43, 134, 133]); | |||
expect(hexToRGBColor('1e1')).toEqual([17, 238, 17]); | |||
expect(hexToRGBColor('#1e1')).toEqual([17, 238, 17]); | |||
expect(hexToRGBColor('1e16')).toEqual([17, 238, 17]); | |||
expect(hexToRGBColor('3bb6b3')).toEqual([59, 182, 179]); | |||
expect(hexToRGBColor('#3bb6b399')).toEqual([59, 182, 179]); | |||
expect(hexToRGBColor('#0')).toEqual([0, 0, 0]); | |||
expect(hexToRGBColor('#00000')).toEqual([0, 0, 0]); | |||
expect(hexToRGBColor('#1234567')).toEqual([0, 0, 0]); | |||
}); | |||
import {useLightTextOnBackground} from './color.js'; | |||
test('useLightTextOnBackground', () => { | |||
expect(useLightTextOnBackground(215, 58, 74)).toBe(true); |