@@ -19,6 +19,7 @@ | |||
*/ | |||
import styled from '@emotion/styled'; | |||
import * as React from 'react'; | |||
import tw from 'twin.macro'; | |||
import { themeColor } from '../helpers'; | |||
export interface MessageFormatting { | |||
@@ -39,7 +40,7 @@ export interface IssueMessageHighlightingProps { | |||
export function IssueMessageHighlighting(props: IssueMessageHighlightingProps) { | |||
const { message, messageFormattings } = props; | |||
if (!message) { | |||
if (message === undefined || message === '') { | |||
return null; | |||
} | |||
@@ -96,7 +97,7 @@ export function IssueMessageHighlighting(props: IssueMessageHighlightingProps) { | |||
<React.Fragment key={`${message}-${start}-${end}`}> | |||
{message.slice(beginning, start)} | |||
{type === MessageFormattingType.CODE ? ( | |||
<SingleLineSnippet className="sw-code sw-rounded-1 sw-py-1/2 sw-px-1 sw-border sw-border-solid"> | |||
<SingleLineSnippet className="sw-code sw-rounded-1 sw-px-1 sw-border sw-border-solid"> | |||
{message.slice(start, end)} | |||
</SingleLineSnippet> | |||
) : ( | |||
@@ -115,4 +116,9 @@ const SingleLineSnippet = styled.span` | |||
background: ${themeColor('codeSnippetBackground')}; | |||
border-color: ${themeColor('codeSnippetBorder')}; | |||
color: ${themeColor('codeSnippetInline')}; | |||
${tw`sw-py-1/2`} | |||
a & { | |||
${tw`sw-pb-0`} | |||
} | |||
`; |
@@ -28,18 +28,24 @@ exports[`should format the string with highlights 5`] = ` | |||
background: rgb(252,252,253); | |||
border-color: rgb(225,230,243); | |||
color: rgb(62,67,87); | |||
padding-top: 0.125rem; | |||
padding-bottom: 0.125rem; | |||
} | |||
a .emotion-0 { | |||
padding-bottom: 0; | |||
} | |||
<span> | |||
m | |||
<span | |||
class="sw-code sw-rounded-1 sw-py-1/2 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
class="sw-code sw-rounded-1 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
> | |||
ess | |||
</span> | |||
a | |||
<span | |||
class="sw-code sw-rounded-1 sw-py-1/2 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
class="sw-code sw-rounded-1 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
> | |||
g | |||
</span> | |||
@@ -62,12 +68,18 @@ exports[`should format the string with highlights 7`] = ` | |||
background: rgb(252,252,253); | |||
border-color: rgb(225,230,243); | |||
color: rgb(62,67,87); | |||
padding-top: 0.125rem; | |||
padding-bottom: 0.125rem; | |||
} | |||
a .emotion-0 { | |||
padding-bottom: 0; | |||
} | |||
<span> | |||
a somewhat longer message with overlapping range | |||
<span | |||
class="sw-code sw-rounded-1 sw-py-1/2 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
class="sw-code sw-rounded-1 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
> | |||
s | |||
</span> | |||
@@ -89,12 +101,18 @@ exports[`should format the string with highlights 9`] = ` | |||
background: rgb(252,252,253); | |||
border-color: rgb(225,230,243); | |||
color: rgb(62,67,87); | |||
padding-top: 0.125rem; | |||
padding-bottom: 0.125rem; | |||
} | |||
a .emotion-0 { | |||
padding-bottom: 0; | |||
} | |||
<span> | |||
a | |||
<span | |||
class="sw-code sw-rounded-1 sw-py-1/2 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
class="sw-code sw-rounded-1 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
> | |||
somewhat longer message | |||
</span> | |||
@@ -109,12 +127,18 @@ exports[`should format the string with highlights 10`] = ` | |||
background: rgb(252,252,253); | |||
border-color: rgb(225,230,243); | |||
color: rgb(62,67,87); | |||
padding-top: 0.125rem; | |||
padding-bottom: 0.125rem; | |||
} | |||
a .emotion-0 { | |||
padding-bottom: 0; | |||
} | |||
<span> | |||
a | |||
<span | |||
class="sw-code sw-rounded-1 sw-py-1/2 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
class="sw-code sw-rounded-1 sw-px-1 sw-border sw-border-solid emotion-0 emotion-1" | |||
> | |||
somewhat longer message with | |||
</span> |
@@ -18,7 +18,13 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import styled from '@emotion/styled'; | |||
import { BareButton, SubnavigationItem, themeColor, themeContrast } from 'design-system'; | |||
import { | |||
BareButton, | |||
IssueMessageHighlighting, | |||
SubnavigationItem, | |||
themeColor, | |||
themeContrast, | |||
} from 'design-system'; | |||
import { noop } from 'lodash'; | |||
import * as React from 'react'; | |||
import { Issue } from '../../../types/types'; | |||
@@ -60,7 +66,10 @@ export default function SubnavigationIssue(props: ConciseIssueProps) { | |||
> | |||
<div className="sw-w-full"> | |||
<StyledIssueTitle aria-current={selected} className="sw-mb-2"> | |||
{issue.message} | |||
<IssueMessageHighlighting | |||
message={issue.message} | |||
messageFormattings={issue.messageFormattings} | |||
/> | |||
</StyledIssueTitle> | |||
<IssueInfo className="sw-flex sw-justify-between sw-gap-2"> | |||
<IssueItemLocationsQuantity issue={issue} /> |
@@ -21,6 +21,7 @@ import { withTheme } from '@emotion/react'; | |||
import styled from '@emotion/styled'; | |||
import { | |||
ClipboardIconButton, | |||
IssueMessageHighlighting, | |||
LAYOUT_GLOBAL_NAV_HEIGHT, | |||
LAYOUT_PROJECT_NAV_HEIGHT, | |||
LightLabel, | |||
@@ -33,7 +34,6 @@ import { | |||
themeShadow, | |||
} from 'design-system'; | |||
import React from 'react'; | |||
import { IssueMessageHighlighting } from '../../../components/issue/IssueMessageHighlighting'; | |||
import { getBranchLikeQuery } from '../../../helpers/branch-like'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { |
@@ -17,9 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { LineFinding } from 'design-system'; | |||
import { IssueMessageHighlighting, LineFinding } from 'design-system'; | |||
import * as React from 'react'; | |||
import { IssueMessageHighlighting } from '../../../components/issue/IssueMessageHighlighting'; | |||
import { Hotspot } from '../../../types/security-hotspots'; | |||
const SCROLL_DELAY = 100; |
@@ -1,102 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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. | |||
*/ | |||
import classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { MessageFormatting, MessageFormattingType } from '../../types/issues'; | |||
export interface IssueMessageHighlightingProps { | |||
message?: string; | |||
messageFormattings?: MessageFormatting[]; | |||
} | |||
export function IssueMessageHighlighting(props: IssueMessageHighlightingProps) { | |||
const { message, messageFormattings } = props; | |||
if (!message) { | |||
return null; | |||
} | |||
if (!(messageFormattings && messageFormattings.length > 0)) { | |||
return <>{message}</>; | |||
} | |||
let previousEnd = 0; | |||
const sanitizedFormattings = [...messageFormattings] | |||
.sort((a, b) => a.start - b.start) | |||
.reduce((acc, messageFormatting) => { | |||
const { type } = messageFormatting; | |||
if (type !== MessageFormattingType.CODE) { | |||
return acc; | |||
} | |||
const { start } = messageFormatting; | |||
let { end } = messageFormatting; | |||
end = Math.min(message.length, end); | |||
if (start < 0 || end === start || end < start) { | |||
return acc; | |||
} | |||
if (acc.length > 0) { | |||
const { start: previousStart, end: previousEnd } = acc[acc.length - 1]; | |||
if (start <= previousEnd) { | |||
acc[acc.length - 1] = { | |||
start: previousStart, | |||
end: Math.max(previousEnd, end), | |||
type, | |||
}; | |||
return acc; | |||
} | |||
} | |||
acc.push({ start, end, type }); | |||
return acc; | |||
}, [] as typeof messageFormattings); | |||
return ( | |||
<span> | |||
{sanitizedFormattings.map(({ start, end, type }) => { | |||
const beginning = previousEnd; | |||
previousEnd = end; | |||
return ( | |||
<React.Fragment key={`${message}-${start}-${end}`}> | |||
{message.slice(beginning, start)} | |||
<span | |||
className={classNames({ | |||
'issue-message-highlight-CODE': type === MessageFormattingType.CODE, | |||
})} | |||
> | |||
{message.slice(start, end)} | |||
</span> | |||
</React.Fragment> | |||
); | |||
})} | |||
{message.slice(previousEnd)} | |||
</span> | |||
); | |||
} |
@@ -1,74 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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. | |||
*/ | |||
import React from 'react'; | |||
import { renderComponent } from '../../../helpers/testReactTestingUtils'; | |||
import { MessageFormattingType } from '../../../types/issues'; | |||
import { | |||
IssueMessageHighlighting, | |||
IssueMessageHighlightingProps, | |||
} from '../IssueMessageHighlighting'; | |||
it.each([ | |||
[undefined, undefined], | |||
['message', undefined], | |||
['message', []], | |||
['message', [{ start: 1, end: 4, type: 'something else' as MessageFormattingType }]], | |||
[ | |||
'message', | |||
[ | |||
{ start: 5, end: 6, type: MessageFormattingType.CODE }, | |||
{ start: 1, end: 4, type: MessageFormattingType.CODE }, | |||
], | |||
], | |||
[ | |||
'a somewhat longer message with overlapping ranges', | |||
[{ start: -1, end: 1, type: MessageFormattingType.CODE }], | |||
], | |||
[ | |||
'a somewhat longer message with overlapping ranges', | |||
[{ start: 48, end: 70, type: MessageFormattingType.CODE }], | |||
], | |||
[ | |||
'a somewhat longer message with overlapping ranges', | |||
[{ start: 0, end: 0, type: MessageFormattingType.CODE }], | |||
], | |||
[ | |||
'a somewhat longer message with overlapping ranges', | |||
[ | |||
{ start: 11, end: 17, type: MessageFormattingType.CODE }, | |||
{ start: 2, end: 25, type: MessageFormattingType.CODE }, | |||
{ start: 25, end: 2, type: MessageFormattingType.CODE }, | |||
], | |||
], | |||
[ | |||
'a somewhat longer message with overlapping ranges', | |||
[ | |||
{ start: 18, end: 30, type: MessageFormattingType.CODE }, | |||
{ start: 2, end: 25, type: MessageFormattingType.CODE }, | |||
], | |||
], | |||
])('should format the string with highlights', (message, messageFormattings) => { | |||
const { asFragment } = renderIssueMessageHighlighting({ message, messageFormattings }); | |||
expect(asFragment()).toMatchSnapshot(); | |||
}); | |||
function renderIssueMessageHighlighting(props: Partial<IssueMessageHighlightingProps> = {}) { | |||
return renderComponent(<IssueMessageHighlighting {...props} />); | |||
} |
@@ -1,100 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should format the string with highlights 1`] = `<DocumentFragment />`; | |||
exports[`should format the string with highlights 2`] = ` | |||
<DocumentFragment> | |||
message | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 3`] = ` | |||
<DocumentFragment> | |||
message | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 4`] = ` | |||
<DocumentFragment> | |||
<span> | |||
message | |||
</span> | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 5`] = ` | |||
<DocumentFragment> | |||
<span> | |||
m | |||
<span | |||
class="issue-message-highlight-CODE" | |||
> | |||
ess | |||
</span> | |||
a | |||
<span | |||
class="issue-message-highlight-CODE" | |||
> | |||
g | |||
</span> | |||
e | |||
</span> | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 6`] = ` | |||
<DocumentFragment> | |||
<span> | |||
a somewhat longer message with overlapping ranges | |||
</span> | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 7`] = ` | |||
<DocumentFragment> | |||
<span> | |||
a somewhat longer message with overlapping range | |||
<span | |||
class="issue-message-highlight-CODE" | |||
> | |||
s | |||
</span> | |||
</span> | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 8`] = ` | |||
<DocumentFragment> | |||
<span> | |||
a somewhat longer message with overlapping ranges | |||
</span> | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 9`] = ` | |||
<DocumentFragment> | |||
<span> | |||
a | |||
<span | |||
class="issue-message-highlight-CODE" | |||
> | |||
somewhat longer message | |||
</span> | |||
with overlapping ranges | |||
</span> | |||
</DocumentFragment> | |||
`; | |||
exports[`should format the string with highlights 10`] = ` | |||
<DocumentFragment> | |||
<span> | |||
a | |||
<span | |||
class="issue-message-highlight-CODE" | |||
> | |||
somewhat longer message with | |||
</span> | |||
overlapping ranges | |||
</span> | |||
</DocumentFragment> | |||
`; |
@@ -17,7 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { StandoutLink } from 'design-system'; | |||
import { IssueMessageHighlighting, StandoutLink } from 'design-system'; | |||
import * as React from 'react'; | |||
import { ComponentContext } from '../../../app/components/componentContext/ComponentContext'; | |||
import { areMyIssuesSelected, parseQuery, serializeQuery } from '../../../apps/issues/utils'; | |||
@@ -27,7 +27,6 @@ import { getComponentIssuesUrl, getIssuesUrl } from '../../../helpers/urls'; | |||
import { BranchLike } from '../../../types/branch-like'; | |||
import { Issue } from '../../../types/types'; | |||
import { useLocation } from '../../hoc/withRouter'; | |||
import { IssueMessageHighlighting } from '../IssueMessageHighlighting'; | |||
export interface IssueMessageProps { | |||
issue: Issue; |
@@ -19,12 +19,11 @@ | |||
*/ | |||
import styled from '@emotion/styled'; | |||
import classNames from 'classnames'; | |||
import { LocationMarker, StyledMarker, themeColor } from 'design-system'; | |||
import { IssueMessageHighlighting, LocationMarker, StyledMarker, themeColor } from 'design-system'; | |||
import * as React from 'react'; | |||
import { translateWithParameters } from '../../helpers/l10n'; | |||
import { MessageFormatting } from '../../types/issues'; | |||
import LocationMessage from '../common/LocationMessage'; | |||
import { IssueMessageHighlighting } from '../issue/IssueMessageHighlighting'; | |||
import './SingleFileLocationNavigator.css'; | |||
interface Props { |