}
export default class Facet extends React.PureComponent<Props> {
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { values } = this.props;
let newValue;
if (this.props.singleSelection) {
const value = values.length ? values[0] : undefined;
newValue = itemValue === value ? undefined : itemValue;
- } else {
+ } else if (multiple) {
newValue = orderBy(
values.includes(itemValue) ? without(values, itemValue) : [...values, itemValue]
);
+ } else {
+ newValue = values.includes(itemValue) && values.length < 2 ? [] : [itemValue];
}
this.props.onChange({ [this.props.property]: newValue });
};
name={renderName(value)}
onClick={this.handleItemClick}
stat={stat && formatMeasure(stat, 'SHORT_INT')}
+ tooltip={this.props.values.length === 1 && !active}
value={value}
/>
);
/>
{groupByDomains(this.props.measures).map(domain => (
<DomainFacet
- key={domain.name}
domain={domain}
+ key={domain.name}
onChange={this.changeMetric}
onToggle={this.toggleFacet}
open={this.state.openFacets[domain.name] === true}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
+ const { assignees } = this.props;
if (itemValue === '') {
// unassigned
this.props.onChange({ assigned: !this.props.assigned, assignees: [] });
- } else {
- // defined assignee
- const { assignees } = this.props;
+ } else if (multiple) {
const newValue = sortBy(
assignees.includes(itemValue) ? without(assignees, itemValue) : [...assignees, itemValue]
);
- this.props.onChange({ assigned: true, assignees: newValue });
+ this.props.onChange({ assigned: true, [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ assigned: true,
+ [this.property]: assignees.includes(itemValue) && assignees.length < 2 ? [] : [itemValue]
+ });
}
};
name={this.getAssigneeName(assignee)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(assignee), this.props.facetMode)}
+ tooltip={this.props.assignees.length === 1 && !this.isAssigneeActive(assignee)}
value={assignee}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { authors } = this.props;
- const newValue = sortBy(
- authors.includes(itemValue) ? without(authors, itemValue) : [...authors, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = sortBy(
+ authors.includes(itemValue) ? without(authors, itemValue) : [...authors, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: authors.includes(itemValue) && authors.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={author}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(author), this.props.facetMode)}
+ tooltip={this.props.authors.length === 1 && !this.props.authors.includes(author)}
value={author}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { directories } = this.props;
- const newValue = sortBy(
- directories.includes(itemValue)
- ? without(directories, itemValue)
- : [...directories, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = sortBy(
+ directories.includes(itemValue)
+ ? without(directories, itemValue)
+ : [...directories, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]:
+ directories.includes(itemValue) && directories.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={this.renderName(directory)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(directory), this.props.facetMode)}
+ tooltip={
+ this.props.directories.length === 1 && !this.props.directories.includes(directory)
+ }
value={directory}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { files } = this.props;
- const newValue = sortBy(
- files.includes(itemValue) ? without(files, itemValue) : [...files, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = sortBy(
+ files.includes(itemValue) ? without(files, itemValue) : [...files, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: files.includes(itemValue) && files.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={this.renderName(file)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(file), this.props.facetMode)}
+ tooltip={this.props.files.length === 1 && !this.props.files.includes(file)}
value={file}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { languages } = this.props;
- const newValue = sortBy(
- languages.includes(itemValue) ? without(languages, itemValue) : [...languages, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = sortBy(
+ languages.includes(itemValue) ? without(languages, itemValue) : [...languages, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: languages.includes(itemValue) && languages.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={this.getLanguageName(language)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(language), this.props.facetMode)}
+ tooltip={this.props.languages.length === 1 && !this.props.languages.includes(language)}
value={language}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { modules } = this.props;
- const newValue = sortBy(
- modules.includes(itemValue) ? without(modules, itemValue) : [...modules, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = sortBy(
+ modules.includes(itemValue) ? without(modules, itemValue) : [...modules, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: modules.includes(itemValue) && modules.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={this.renderName(module)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(module), this.props.facetMode)}
+ tooltip={this.props.modules.length === 1 && !this.props.modules.includes(module)}
value={module}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { projects } = this.props;
- const newValue = sortBy(
- projects.includes(itemValue) ? without(projects, itemValue) : [...projects, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = sortBy(
+ projects.includes(itemValue) ? without(projects, itemValue) : [...projects, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: projects.includes(itemValue) && projects.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={this.renderName(project)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(project), this.props.facetMode)}
+ tooltip={this.props.projects.length === 1 && !this.props.projects.includes(project)}
value={project}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
+ const { resolutions } = this.props;
if (itemValue === '') {
// unresolved
this.props.onChange({ resolved: !this.props.resolved, resolutions: [] });
- } else {
- // defined resolution
- const { resolutions } = this.props;
+ } else if (multiple) {
const newValue = orderBy(
resolutions.includes(itemValue)
? without(resolutions, itemValue)
: [...resolutions, itemValue]
);
- this.props.onChange({ resolved: true, resolutions: newValue });
+ this.props.onChange({ resolved: true, [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ resolved: true,
+ [this.property]:
+ resolutions.includes(itemValue) && resolutions.length < 2 ? [] : [itemValue]
+ });
}
};
name={this.getFacetItemName(resolution)}
onClick={this.handleItemClick}
stat={formatFacetStat(stat, this.props.facetMode)}
+ tooltip={
+ this.props.resolutions.length === 1 &&
+ resolution !== '' &&
+ !this.props.resolutions.includes(resolution)
+ }
value={resolution}
/>
);
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { rules } = this.props;
- const newValue = sortBy(
- rules.includes(itemValue) ? without(rules, itemValue) : [...rules, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = sortBy(
+ rules.includes(itemValue) ? without(rules, itemValue) : [...rules, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: rules.includes(itemValue) && rules.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={this.getRuleName(rule)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(rule), this.props.facetMode)}
+ tooltip={this.props.rules.length === 1 && !this.props.rules.includes(rule)}
value={rule}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { severities } = this.props;
- const newValue = orderBy(
- severities.includes(itemValue) ? without(severities, itemValue) : [...severities, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = orderBy(
+ severities.includes(itemValue) ? without(severities, itemValue) : [...severities, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: severities.includes(itemValue) && severities.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={<SeverityHelper severity={severity} />}
onClick={this.handleItemClick}
stat={formatFacetStat(stat, this.props.facetMode)}
+ tooltip={this.props.severities.length === 1 && !this.props.severities.includes(severity)}
value={severity}
/>
);
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { statuses } = this.props;
- const newValue = orderBy(
- statuses.includes(itemValue) ? without(statuses, itemValue) : [...statuses, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = orderBy(
+ statuses.includes(itemValue) ? without(statuses, itemValue) : [...statuses, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: statuses.includes(itemValue) && statuses.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={<StatusHelper resolution={undefined} status={status} />}
onClick={this.handleItemClick}
stat={formatFacetStat(stat, this.props.facetMode)}
+ tooltip={this.props.statuses.length === 1 && !this.props.statuses.includes(status)}
value={status}
/>
);
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { tags } = this.props;
- const newValue = sortBy(
- tags.includes(itemValue) ? without(tags, itemValue) : [...tags, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const { tags } = this.props;
+ const newValue = sortBy(
+ tags.includes(itemValue) ? without(tags, itemValue) : [...tags, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: tags.includes(itemValue) && tags.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
name={this.renderTag(tag)}
onClick={this.handleItemClick}
stat={formatFacetStat(this.getStat(tag), this.props.facetMode)}
+ tooltip={this.props.tags.length === 1 && !this.props.tags.includes(tag)}
value={tag}
/>
))}
open: true
};
- handleItemClick = (itemValue: string) => {
+ handleItemClick = (itemValue: string, multiple: boolean) => {
const { types } = this.props;
- const newValue = orderBy(
- types.includes(itemValue) ? without(types, itemValue) : [...types, itemValue]
- );
- this.props.onChange({ [this.property]: newValue });
+ if (multiple) {
+ const newValue = orderBy(
+ types.includes(itemValue) ? without(types, itemValue) : [...types, itemValue]
+ );
+ this.props.onChange({ [this.property]: newValue });
+ } else {
+ this.props.onChange({
+ [this.property]: types.includes(itemValue) && types.length < 2 ? [] : [itemValue]
+ });
+ }
};
handleHeaderClick = () => {
}
onClick={this.handleItemClick}
stat={formatFacetStat(stat, this.props.facetMode)}
+ tooltip={this.props.types.length === 1 && !this.props.types.includes(type)}
value={type}
/>
);
open={true}
organization={undefined}
referencedUsers={{ foo: { avatar: 'avatart-foo', name: 'name-foo' } }}
- stats={{ '': 5, foo: 13, bar: 7 }}
+ stats={{ '': 5, foo: 13, bar: 7, baz: 6 }}
{...props}
/>
);
expect(onChange).lastCalledWith({ assigned: false, assignees: [] });
itemOnClick('bar');
- expect(onChange).lastCalledWith({ assigned: true, assignees: ['bar', 'foo'] });
+ expect(onChange).lastCalledWith({ assigned: true, assignees: ['bar'] });
- itemOnClick('foo');
- expect(onChange).lastCalledWith({ assigned: true, assignees: [] });
+ itemOnClick('baz', true);
+ expect(onChange).lastCalledWith({ assigned: true, assignees: ['baz', 'foo'] });
});
it('should call onToggle', () => {
name="unassigned"
onClick={[Function]}
stat="5"
+ tooltip={false}
value=""
/>
<FacetItem
}
onClick={[Function]}
stat="13"
+ tooltip={false}
value="foo"
/>
<FacetItem
name="bar"
onClick={[Function]}
stat="7"
+ tooltip={false}
value="bar"
/>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="baz"
+ loading={false}
+ name="baz"
+ onClick={[Function]}
+ stat="6"
+ tooltip={false}
+ value="baz"
+ />
</FacetItemsList>
<FacetFooter
onSearch={[Function]}
name="unassigned"
onClick={[Function]}
stat="5"
+ tooltip={false}
value=""
/>
<FacetItem
}
onClick={[Function]}
stat="13"
+ tooltip={false}
value="foo"
/>
<FacetItem
name="bar"
onClick={[Function]}
stat="7"
+ tooltip={false}
value="bar"
/>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="baz"
+ loading={false}
+ name="baz"
+ onClick={[Function]}
+ stat="6"
+ tooltip={false}
+ value="baz"
+ />
</FacetItemsList>
<FacetFooter
onSearch={[Function]}
name="unassigned"
onClick={[Function]}
stat="5"
+ tooltip={true}
value=""
/>
<FacetItem
}
onClick={[Function]}
stat="13"
+ tooltip={false}
value="foo"
/>
<FacetItem
name="bar"
onClick={[Function]}
stat="7"
+ tooltip={true}
value="bar"
/>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="baz"
+ loading={false}
+ name="baz"
+ onClick={[Function]}
+ stat="6"
+ tooltip={true}
+ value="baz"
+ />
</FacetItemsList>
<FacetFooter
onSearch={[Function]}
);
}
- handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.currentTarget.blur();
let urlOption;
if (option) {
- if (Array.isArray(value)) {
+ if (Array.isArray(value) && event.ctrlKey) {
if (this.isSelected(option)) {
urlOption = value.length > 1 ? value.filter(val => val !== option).join(',') : null;
} else {
urlOption = value.concat(option).join(',');
}
} else {
- urlOption = this.isSelected(option) ? null : option;
+ urlOption =
+ this.isSelected(option) && (!Array.isArray(value) || value.length < 2) ? null : option;
}
this.props.onQueryChange({ [property]: urlOption });
*/
import * as React from 'react';
import * as classNames from 'classnames';
+import { isOnMac } from './utils';
+import Tooltip from '../controls/Tooltip';
+import { translate } from '../../helpers/l10n';
export interface Props {
active?: boolean;
halfWidth?: boolean;
loading?: boolean;
name: React.ReactNode;
- onClick: (x: string) => void;
+ onClick: (x: string, multiple?: boolean) => void;
stat?: React.ReactNode;
+ tooltip?: boolean;
value: string;
}
loading: false
};
- handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.currentTarget.blur();
- this.props.onClick(this.props.value);
+ this.props.onClick(this.props.value, event.ctrlKey || event.metaKey);
};
render() {
'search-navigator-facet-half': this.props.halfWidth
});
- return this.props.disabled ? (
- <span className={className} data-facet={this.props.value}>
- <span className="facet-name">{this.props.name}</span>
- {this.props.stat != null && (
- <span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span>
- )}
- </span>
- ) : (
- <a className={className} data-facet={this.props.value} href="#" onClick={this.handleClick}>
- <span className="facet-name">{this.props.name}</span>
- {this.props.stat != null && (
- <span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span>
+ const overlay =
+ this.props.tooltip && !this.props.disabled
+ ? translate(
+ isOnMac()
+ ? 'shortcuts.section.global.facets.multiselection.mac'
+ : 'shortcuts.section.global.facets.multiselection'
+ )
+ : undefined;
+
+ return (
+ <Tooltip overlay={overlay} placement="right">
+ {this.props.disabled ? (
+ <span className={className} data-facet={this.props.value}>
+ <span className="facet-name">{this.props.name}</span>
+ {this.props.stat != null && (
+ <span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span>
+ )}
+ </span>
+ ) : (
+ <a
+ className={className}
+ data-facet={this.props.value}
+ href="#"
+ onClick={this.handleClick}>
+ <span className="facet-name">{this.props.name}</span>
+ {this.props.stat != null && (
+ <span className="facet-stat">{this.props.loading ? '' : this.props.stat}</span>
+ )}
+ </a>
)}
- </a>
+ </Tooltip>
);
}
}
it('should call onClick', () => {
const onClick = jest.fn();
const wrapper = renderFacetItem({ onClick });
- click(wrapper, { currentTarget: { blur() {}, dataset: { value: 'bar' } } });
+ click(wrapper.find('a'), { currentTarget: { blur() {}, dataset: { value: 'bar' } } });
expect(onClick).toHaveBeenCalled();
});
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should loading stat 1`] = `
-<a
- className="facet search-navigator-facet"
- data-facet="bar"
- href="#"
- onClick={[Function]}
+<Tooltip
+ placement="right"
>
- <span
- className="facet-name"
+ <a
+ className="facet search-navigator-facet"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
>
- foo
- </span>
-</a>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+ </a>
+</Tooltip>
`;
exports[`should render active 1`] = `
-<a
- className="facet search-navigator-facet active"
- data-facet="bar"
- href="#"
- onClick={[Function]}
+<Tooltip
+ placement="right"
>
- <span
- className="facet-name"
+ <a
+ className="facet search-navigator-facet active"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
>
- foo
- </span>
-</a>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+ </a>
+</Tooltip>
`;
exports[`should render disabled 1`] = `
-<span
- className="facet search-navigator-facet"
- data-facet="bar"
+<Tooltip
+ placement="right"
>
<span
- className="facet-name"
+ className="facet search-navigator-facet"
+ data-facet="bar"
>
- foo
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
</span>
-</span>
+</Tooltip>
`;
exports[`should render half width 1`] = `
-<a
- className="facet search-navigator-facet search-navigator-facet-half"
- data-facet="bar"
- href="#"
- onClick={[Function]}
+<Tooltip
+ placement="right"
>
- <span
- className="facet-name"
+ <a
+ className="facet search-navigator-facet search-navigator-facet-half"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
>
- foo
- </span>
-</a>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+ </a>
+</Tooltip>
`;
exports[`should render inactive 1`] = `
-<a
- className="facet search-navigator-facet"
- data-facet="bar"
- href="#"
- onClick={[Function]}
+<Tooltip
+ placement="right"
>
- <span
- className="facet-name"
+ <a
+ className="facet search-navigator-facet"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
>
- foo
- </span>
-</a>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+ </a>
+</Tooltip>
`;
exports[`should render stat 1`] = `
-<a
- className="facet search-navigator-facet"
- data-facet="bar"
- href="#"
- onClick={[Function]}
+<Tooltip
+ placement="right"
>
- <span
- className="facet-name"
- >
- foo
- </span>
- <span
- className="facet-stat"
+ <a
+ className="facet search-navigator-facet"
+ data-facet="bar"
+ href="#"
+ onClick={[Function]}
>
- 13
- </span>
-</a>
+ <span
+ className="facet-name"
+ >
+ foo
+ </span>
+ <span
+ className="facet-stat"
+ >
+ 13
+ </span>
+ </a>
+</Tooltip>
`;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+export function isOnMac() {
+ return navigator.userAgent.indexOf('Mac OS X') !== -1;
+}
shortcuts.section.global=Global
shortcuts.section.global.search=quickly open search bar
shortcuts.section.global.shortcuts=open this window
+shortcuts.section.global.facets.multiselection=Ctrl + click to add to selection
+shortcuts.section.global.facets.multiselection.mac=Cmd + click to add to selection
shortcuts.section.issues=Issues Page
shortcuts.section.issues.navigate_between_issues=navigate between issues