summaryrefslogtreecommitdiffstats
path: root/web_src
diff options
context:
space:
mode:
Diffstat (limited to 'web_src')
-rw-r--r--web_src/js/components/ContextPopup.vue23
-rw-r--r--web_src/js/features/common-issue.js5
-rw-r--r--web_src/js/features/comp/LabelEdit.js69
-rw-r--r--web_src/js/features/repo-legacy.js74
-rw-r--r--web_src/js/features/repo-projects.js20
-rw-r--r--web_src/js/utils.js15
-rw-r--r--web_src/less/_base.less12
-rw-r--r--web_src/less/_repository.less54
8 files changed, 179 insertions, 93 deletions
diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue
index 07c73ff5cf..3244034782 100644
--- a/web_src/js/components/ContextPopup.vue
+++ b/web_src/js/components/ContextPopup.vue
@@ -26,25 +26,10 @@
<script>
import $ from 'jquery';
import {SvgIcon} from '../svg.js';
+import {useLightTextOnBackground} from '../utils.js';
const {appSubUrl, i18n} = window.config;
-// NOTE: see models/issue_label.go for similar implementation
-const srgbToLinear = (color) => {
- color /= 255;
- if (color <= 0.04045) {
- return color / 12.92;
- }
- return ((color + 0.055) / 1.055) ** 2.4;
-};
-const luminance = (colorString) => {
- const r = srgbToLinear(parseInt(colorString.substring(0, 2), 16));
- const g = srgbToLinear(parseInt(colorString.substring(2, 4), 16));
- const b = srgbToLinear(parseInt(colorString.substring(4, 6), 16));
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
-};
-const luminanceThreshold = 0.179;
-
export default {
components: {SvgIcon},
data: () => ({
@@ -92,10 +77,10 @@ export default {
labels() {
return this.issue.labels.map((label) => {
let textColor;
- if (luminance(label.color) < luminanceThreshold) {
- textColor = '#ffffff';
+ if (useLightTextOnBackground(label.color)) {
+ textColor = '#eeeeee';
} else {
- textColor = '#000000';
+ textColor = '#111111';
}
return {name: label.name, color: `#${label.color}`, textColor};
});
diff --git a/web_src/js/features/common-issue.js b/web_src/js/features/common-issue.js
index 4a62089c60..f53dd5081b 100644
--- a/web_src/js/features/common-issue.js
+++ b/web_src/js/features/common-issue.js
@@ -32,7 +32,7 @@ export function initCommonIssue() {
syncIssueSelectionState();
});
- $('.issue-action').on('click', async function () {
+ $('.issue-action').on('click', async function (e) {
let action = this.getAttribute('data-action');
let elementId = this.getAttribute('data-element-id');
const url = this.getAttribute('data-url');
@@ -43,6 +43,9 @@ export function initCommonIssue() {
elementId = '';
action = 'clear';
}
+ if (action === 'toggle' && e.altKey) {
+ action = 'toggle-alt';
+ }
updateIssuesMeta(
url,
action,
diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js
index df294078fa..313d406821 100644
--- a/web_src/js/features/comp/LabelEdit.js
+++ b/web_src/js/features/comp/LabelEdit.js
@@ -1,26 +1,64 @@
import $ from 'jquery';
import {initCompColorPicker} from './ColorPicker.js';
+function isExclusiveScopeName(name) {
+ return /.*[^/]\/[^/].*/.test(name);
+}
+
+function updateExclusiveLabelEdit(form) {
+ const nameInput = $(`${form} .label-name-input`);
+ const exclusiveField = $(`${form} .label-exclusive-input-field`);
+ const exclusiveCheckbox = $(`${form} .label-exclusive-input`);
+ const exclusiveWarning = $(`${form} .label-exclusive-warning`);
+
+ if (isExclusiveScopeName(nameInput.val())) {
+ exclusiveField.removeClass('muted');
+ if (exclusiveCheckbox.prop('checked') && exclusiveCheckbox.data('exclusive-warn')) {
+ exclusiveWarning.removeClass('gt-hidden');
+ } else {
+ exclusiveWarning.addClass('gt-hidden');
+ }
+ } else {
+ exclusiveField.addClass('muted');
+ exclusiveWarning.addClass('gt-hidden');
+ }
+}
+
export function initCompLabelEdit(selector) {
if (!$(selector).length) return;
+ initCompColorPicker();
+
// Create label
- const $newLabelPanel = $('.new-label.segment');
$('.new-label.button').on('click', () => {
- $newLabelPanel.show();
- });
- $('.new-label.segment .cancel').on('click', () => {
- $newLabelPanel.hide();
+ updateExclusiveLabelEdit('.new-label');
+ $('.new-label.modal').modal({
+ onApprove() {
+ $('.new-label.form').trigger('submit');
+ }
+ }).modal('show');
+ return false;
});
- initCompColorPicker();
-
+ // Edit label
$('.edit-label-button').on('click', function () {
$('.edit-label .color-picker').minicolors('value', $(this).data('color'));
$('#label-modal-id').val($(this).data('id'));
- $('.edit-label .new-label-input').val($(this).data('title'));
- $('.edit-label .new-label-desc-input').val($(this).data('description'));
+
+ const nameInput = $('.edit-label .label-name-input');
+ nameInput.val($(this).data('title'));
+
+ const exclusiveCheckbox = $('.edit-label .label-exclusive-input');
+ exclusiveCheckbox.prop('checked', this.hasAttribute('data-exclusive'));
+ // Warn when label was previously not exclusive and used in issues
+ exclusiveCheckbox.data('exclusive-warn',
+ $(this).data('num-issues') > 0 &&
+ (!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.modal').modal({
onApprove() {
$('.edit-label.form').trigger('submit');
@@ -28,4 +66,17 @@ export function initCompLabelEdit(selector) {
}).modal('show');
return false;
});
+
+ $('.new-label .label-name-input').on('input', () => {
+ updateExclusiveLabelEdit('.new-label');
+ });
+ $('.new-label .label-exclusive-input').on('change', () => {
+ updateExclusiveLabelEdit('.new-label');
+ });
+ $('.edit-label .label-name-input').on('input', () => {
+ updateExclusiveLabelEdit('.edit-label');
+ });
+ $('.edit-label .label-exclusive-input').on('change', () => {
+ updateExclusiveLabelEdit('.edit-label');
+ });
}
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
index 07c67ba5da..2cf4963b6a 100644
--- a/web_src/js/features/repo-legacy.js
+++ b/web_src/js/features/repo-legacy.js
@@ -110,35 +110,59 @@ export function initRepoCommentForm() {
}
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
- if ($(this).hasClass('checked')) {
- $(this).removeClass('checked');
- $(this).find('.octicon-check').addClass('invisible');
- if (hasUpdateAction) {
- if (!($(this).data('id') in items)) {
- items[$(this).data('id')] = {
- 'update-url': $listMenu.data('update-url'),
- action: 'detach',
- 'issue-id': $listMenu.data('issue-id'),
- };
- } else {
- delete items[$(this).data('id')];
+
+ const clickedItem = $(this);
+ const scope = $(this).attr('data-scope');
+ const canRemoveScope = e.altKey;
+
+ $(this).parent().find('.item').each(function () {
+ if (scope) {
+ // Enable only clicked item for scoped labels
+ if ($(this).attr('data-scope') !== scope) {
+ return true;
}
+ if ($(this).is(clickedItem)) {
+ if (!canRemoveScope && $(this).hasClass('checked')) {
+ return true;
+ }
+ } else if (!$(this).hasClass('checked')) {
+ return true;
+ }
+ } else if (!$(this).is(clickedItem)) {
+ // Toggle for other labels
+ return true;
}
- } else {
- $(this).addClass('checked');
- $(this).find('.octicon-check').removeClass('invisible');
- if (hasUpdateAction) {
- if (!($(this).data('id') in items)) {
- items[$(this).data('id')] = {
- 'update-url': $listMenu.data('update-url'),
- action: 'attach',
- 'issue-id': $listMenu.data('issue-id'),
- };
- } else {
- delete items[$(this).data('id')];
+
+ if ($(this).hasClass('checked')) {
+ $(this).removeClass('checked');
+ $(this).find('.octicon-check').addClass('invisible');
+ if (hasUpdateAction) {
+ if (!($(this).data('id') in items)) {
+ items[$(this).data('id')] = {
+ 'update-url': $listMenu.data('update-url'),
+ action: 'detach',
+ 'issue-id': $listMenu.data('issue-id'),
+ };
+ } else {
+ delete items[$(this).data('id')];
+ }
+ }
+ } else {
+ $(this).addClass('checked');
+ $(this).find('.octicon-check').removeClass('invisible');
+ if (hasUpdateAction) {
+ if (!($(this).data('id') in items)) {
+ items[$(this).data('id')] = {
+ 'update-url': $listMenu.data('update-url'),
+ action: 'attach',
+ 'issue-id': $listMenu.data('issue-id'),
+ };
+ } else {
+ delete items[$(this).data('id')];
+ }
}
}
- }
+ });
// TODO: Which thing should be done for choosing review requests
// to make chosen items be shown on time here?
diff --git a/web_src/js/features/repo-projects.js b/web_src/js/features/repo-projects.js
index f6d6c89816..534f517853 100644
--- a/web_src/js/features/repo-projects.js
+++ b/web_src/js/features/repo-projects.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import {useLightTextOnBackground} from '../utils.js';
const {csrfToken} = window.config;
@@ -183,26 +184,13 @@ export function initRepoProject() {
}
function setLabelColor(label, color) {
- const red = getRelativeColor(parseInt(color.slice(1, 3), 16));
- const green = getRelativeColor(parseInt(color.slice(3, 5), 16));
- const blue = getRelativeColor(parseInt(color.slice(5, 7), 16));
- const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
-
- if (luminance > 0.179) {
- label.removeClass('light-label').addClass('dark-label');
- } else {
+ if (useLightTextOnBackground(color)) {
label.removeClass('dark-label').addClass('light-label');
+ } else {
+ label.removeClass('light-label').addClass('dark-label');
}
}
-/**
- * Inspired by W3C recommendation https://www.w3.org/TR/WCAG20/#relativeluminancedef
- */
-function getRelativeColor(color) {
- color /= 255;
- return color <= 0.03928 ? color / 12.92 : ((color + 0.055) / 1.055) ** 2.4;
-}
-
function rgbToHex(rgb) {
rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+).*\)$/);
return `#${hex(rgb[1])}${hex(rgb[2])}${hex(rgb[3])}`;
diff --git a/web_src/js/utils.js b/web_src/js/utils.js
index c7624404c7..b3ffbf2988 100644
--- a/web_src/js/utils.js
+++ b/web_src/js/utils.js
@@ -146,3 +146,18 @@ export function toAbsoluteUrl(url) {
}
return `${window.location.origin}${url}`;
}
+
+// determine if light or dark text color should be used on a given background color
+// NOTE: see models/issue_label.go for similar implementation
+export function useLightTextOnBackground(backgroundColor) {
+ if (backgroundColor[0] === '#') {
+ backgroundColor = backgroundColor.substring(1);
+ }
+ // Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
+ // In the future WCAG 3 APCA may be a better solution.
+ const r = parseInt(backgroundColor.substring(0, 2), 16);
+ const g = parseInt(backgroundColor.substring(2, 4), 16);
+ const b = parseInt(backgroundColor.substring(4, 6), 16);
+ const brightness = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+ return brightness < 0.35;
+}
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index 4b65ae6812..771049ad39 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -1116,6 +1116,7 @@ a.ui.card:hover,
.ui.modal > .content {
background: var(--color-body);
+ text-align: left !important;
}
.ui.modal > .actions {
@@ -1364,6 +1365,10 @@ a.ui.card:hover,
-webkit-text-fill-color: var(--color-black) !important;
}
+.ui.form .field.muted {
+ opacity: var(--opacity-disabled);
+}
+
.ui.loading.loading.input > i.icon svg {
visibility: hidden;
}
@@ -2568,8 +2573,7 @@ table th[data-sortt-desc] {
border-top: none;
a {
- font-size: 15px;
- padding-top: 5px;
+ font-size: 12px;
padding-right: 10px;
color: var(--color-text-light);
@@ -2581,10 +2585,6 @@ table th[data-sortt-desc] {
margin-right: 30px;
}
}
-
- .ui.label {
- font-size: 1em;
- }
}
.item:last-child {
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index f7087d4d30..b2c4cdcdfb 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -92,7 +92,7 @@
.metas {
.menu {
overflow-x: auto;
- max-height: 300px;
+ max-height: 500px;
}
.ui.list {
@@ -155,12 +155,6 @@
}
.filter.menu {
- .label.color {
- border-radius: 3px;
- margin-left: 15px;
- padding: 0 8px;
- }
-
&.labels {
.label-filter .menu .info {
display: inline-block;
@@ -181,7 +175,7 @@
}
.menu {
- max-height: 300px;
+ max-height: 500px;
overflow-x: auto;
right: 0 !important;
left: auto !important;
@@ -190,7 +184,7 @@
.select-label {
.desc {
- padding-left: 16px;
+ padding-left: 23px;
}
}
@@ -607,7 +601,7 @@
min-width: 220px;
.filter.menu {
- max-height: 300px;
+ max-height: 500px;
overflow-x: auto;
}
}
@@ -2774,7 +2768,7 @@
}
.edit-label.modal,
-.new-label.segment {
+.new-label.modal {
.form {
.column {
padding-right: 0;
@@ -2786,12 +2780,9 @@
}
.color.picker.column {
- width: auto;
-
- .color-picker {
- height: 35px;
- width: auto;
- padding-left: 30px;
+ display: flex;
+ .minicolors {
+ flex: 1;
}
}
@@ -2872,6 +2863,35 @@
line-height: 1.3em; // there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly
}
+// Scoped labels with different colors on left and right, and slanted divider in the middle
+.scope-parent {
+ background: none !important;
+ padding: 0 !important;
+}
+
+.ui.label.scope-left {
+ border-bottom-right-radius: 0;
+ border-top-right-radius: 0;
+ padding-right: 0;
+ margin-right: 0;
+}
+
+.ui.label.scope-middle {
+ width: 12px;
+ border-radius: 0;
+ padding-left: 0;
+ padding-right: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.ui.label.scope-right {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+ padding-left: 0;
+ margin-left: 0;
+}
+
.repo-button-row {
margin-bottom: 10px;
}