Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions config/webpack.config-dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export default {
// switch the source map generation when debugging
// note, we used 'eval-source-map' before, but since webpack 5.100, it breaks the build
// (causes 'SyntaxError: redeclaration of function normalize')
devtool: 'inline-source-map',
//devtool: false, // turn it off completely
// devtool: 'inline-source-map',
devtool: false, // turn it off completely

entry: path.join(__dirname, '..', 'src/client.js'),
output: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Solutions/SolutionFiles/SolutionFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const SolutionFiles = ({
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip id={`openfile-${file.id}`}>
<Tooltip id={`open-file-${file.id}`}>
<FormattedMessage
id="app.solutionFiles.openButton"
defaultMessage="Open preview in dialog"
Expand Down
27 changes: 23 additions & 4 deletions src/components/Solutions/SourceCodeBox/SourceCodeBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer';
import Prism from 'prismjs';

import Box from '../../widgets/Box';
import SourceCodeViewer from '../../helpers/SourceCodeViewer';
import SourceCodeViewer, { SourceCodeHighlightingSelector } from '../../helpers/SourceCodeViewer';
import ResourceRenderer from '../../helpers/ResourceRenderer';
import Icon, { CopyIcon, CopySuccessIcon, CodeCompareIcon, DownloadIcon, LoadingIcon, WarningIcon } from '../../icons';

import { getPrismModeFromExtension } from '../../helpers/syntaxHighlighting.js';
import { getFileExtensionLC, simpleScalarMemoize } from '../../../helpers/common.js';
import { getFileExtensionLC, simpleScalarMemoize, EMPTY_OBJ } from '../../../helpers/common.js';

const normalizeLineEndings = simpleScalarMemoize(content => content.replaceAll('\r', ''));

Expand Down Expand Up @@ -45,10 +45,15 @@ const SourceCodeBox = ({
reviewClosed = false,
collapsable = false,
isOpen = true,
highlightOverrides = EMPTY_OBJ,
setHighlightOverride = null,
}) => {
const res = fileContentsSelector(parentId, entryName);
const [clipboardCopied, setClipboardCopied] = useState(false);
const [onlyComments, setOnlyComments] = useState(false);

const fileExtension = getFileExtensionLC(name);

return (
<ResourceRenderer
key={id}
Expand Down Expand Up @@ -111,7 +116,7 @@ const SourceCodeBox = ({
/>
)}

<code>{name}</code>
<code className="me-2">{name}</code>

{download && (
<DownloadIcon
Expand Down Expand Up @@ -163,6 +168,17 @@ const SourceCodeBox = ({
</>
)}

{setHighlightOverride && (
<SourceCodeHighlightingSelector
id={`${id}-highlighting`}
extension={fileExtension}
initialMode={highlightOverrides[fileExtension]}
onChange={setHighlightOverride}
timid
gapLeft={2}
/>
)}

{diffMode && (
<>
<CodeCompareIcon
Expand Down Expand Up @@ -273,7 +289,7 @@ const SourceCodeBox = ({
oldValue={normalizeLineEndings(content.content)}
newValue={normalizeLineEndings(secondContent.content)}
splitView={true}
renderContent={diffViewHighlightSyntax(getPrismModeFromExtension(getFileExtensionLC(name)))}
renderContent={diffViewHighlightSyntax(getPrismModeFromExtension(fileExtension))}
compareMethod={DiffMethod.WORDS_WITH_SPACE}
/>
</div>
Expand All @@ -291,6 +307,7 @@ const SourceCodeBox = ({
removeComment={removeComment}
reviewClosed={reviewClosed}
onlyComments={reviewClosed && onlyComments}
highlightOverrides={highlightOverrides}
/>
)}
</Box>
Expand Down Expand Up @@ -319,6 +336,8 @@ SourceCodeBox.propTypes = {
reviewClosed: PropTypes.bool,
collapsable: PropTypes.bool,
isOpen: PropTypes.bool,
highlightOverrides: PropTypes.object,
setHighlightOverride: PropTypes.func,
};

export default SourceCodeBox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, { useState, useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Overlay, Popover, FormSelect, ButtonGroup } from 'react-bootstrap';

import Button from '../../widgets/TheButton';
import Icon, { CloseIcon, RefreshIcon, SaveIcon } from '../../icons';

import { getPrismModeFromExtension, PRISM_SUPPORTED_LANGUAGES } from '../../helpers/syntaxHighlighting.js';

export const localStorageHighlightOverridesKey = 'SourceCodeViewer.highlightOverrides';

const SourceCodeHighlightingSelector = ({
id,
fullButton = false,
extension,
initialMode = null,
onChange,
...props
}) => {
const defaultMode = getPrismModeFromExtension(extension);
const target = useRef(null);
const [visible, setVisible] = useState(false);
const [selectedMode, setSelectedMode] = useState(initialMode !== null ? initialMode : defaultMode);

useEffect(() => {
setSelectedMode(initialMode !== null ? initialMode : defaultMode);
}, [initialMode, extension]);

const clickHandler = ev => {
ev.stopPropagation();
setVisible(!visible);
};

const changeHandler = ev => {
setSelectedMode(ev.target.value);
};

return (
<>
{fullButton ? (
<Button {...props} onClick={clickHandler} ref={target}>
<Icon icon="highlighter" gapRight={2} />
{(initialMode !== null ? initialMode : defaultMode) || (
<FormattedMessage id="app.solutionSourceCodes.noHighlighting" defaultMessage="no highlighting" />
)}
</Button>
) : (
<Icon icon="highlighter" {...props} onClick={clickHandler} ref={target} />
)}

<Overlay target={target.current} show={visible} placement="bottom">
{props => (
<Popover id={id} onClick={ev => ev.stopPropagation()} className="highlighting-selector" {...props}>
<Popover.Header>
{extension ? (
<>
<FormattedMessage
id="app.solutionSourceCodes.highlightingTitle"
defaultMessage="Highlighting for files with extension"
/>{' '}
<code>*.{extension}</code>
</>
) : (
<FormattedMessage
id="app.solutionSourceCodes.highlightingTitleNoExtension"
defaultMessage="Highlighting for files without extension"
/>
)}
</Popover.Header>
<Popover.Body className="text-center">
<FormSelect onChange={changeHandler} value={selectedMode}>
<option value="" className={selectedMode === '' ? 'fw-bold text-primary' : 'fw-italic text-muted'}>
[<FormattedMessage id="app.solutionSourceCodes.noHighlighting" defaultMessage="no highlighting" />]
{defaultMode === '' && (
<>
{' ('}
<FormattedMessage id="generic.default" defaultMessage="default" />
{')'}
</>
)}
</option>
{PRISM_SUPPORTED_LANGUAGES.map(lang => (
<option
value={lang}
key={lang}
className={lang === defaultMode || lang === selectedMode ? 'fw-bold text-primary' : ''}>
{lang}
{lang === defaultMode && (
<>
{' ('}
<FormattedMessage id="generic.default" defaultMessage="default" />
{')'}
</>
)}
</option>
))}
</FormSelect>

<ButtonGroup className="mt-3">
<Button
variant="success"
size="sm"
onClick={() => {
onChange(extension, selectedMode !== defaultMode ? selectedMode : null);
setVisible(false);
}}>
<SaveIcon gapRight={2} />
<FormattedMessage id="generic.save" defaultMessage="Save" />
</Button>
{selectedMode !== defaultMode && (
<Button
variant="danger"
size="sm"
onClick={() => {
setSelectedMode(defaultMode);
onChange(extension, null);
}}>
<RefreshIcon gapRight={2} />
<FormattedMessage id="generic.reset" defaultMessage="Reset" />
</Button>
)}
<Button
variant="secondary"
size="sm"
onClick={() => {
setVisible(false);
}}>
<CloseIcon gapRight={2} />
<FormattedMessage id="generic.close" defaultMessage="Close" />
</Button>
</ButtonGroup>
</Popover.Body>
</Popover>
)}
</Overlay>
</>
);
};

SourceCodeHighlightingSelector.propTypes = {
id: PropTypes.string.isRequired,
fullButton: PropTypes.bool,
extension: PropTypes.string.isRequired,
initialMode: PropTypes.string,
onChange: PropTypes.func.isRequired,
};

export default SourceCodeHighlightingSelector;
4 changes: 4 additions & 0 deletions src/components/helpers/SourceCodeViewer/SourceCodeViewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,7 @@ pre .sourceCodeViewerComments, code .sourceCodeViewerComments {
.sourceCodeViewer.addComment .scvAddButton:hover::before {
opacity: 1;
}

.popover.highlighting-selector {
--bs-popover-max-width: 400px;
}
14 changes: 11 additions & 3 deletions src/components/helpers/SourceCodeViewer/SourceCodeViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'prismjs/themes/prism.css';

import ReviewCommentForm, { newCommentFormInitialValues } from '../../forms/ReviewCommentForm';
import { getPrismModeFromExtension } from '../../helpers/syntaxHighlighting.js';
import { getFileExtensionLC, canUseDOM } from '../../../helpers/common.js';
import { getFileExtensionLC, canUseDOM, EMPTY_OBJ } from '../../../helpers/common.js';

import SourceCodeComment from './SourceCodeComment.js';
import './SourceCodeViewer.css';
Expand Down Expand Up @@ -196,10 +196,17 @@ class SourceCodeViewer extends React.Component {
});

render() {
const { name, content = '', addComment } = this.props;
const { name, content = '', addComment, highlightOverrides = EMPTY_OBJ } = this.props;

const extension = getFileExtensionLC(name);
const language =
highlightOverrides[extension] !== undefined
? highlightOverrides[extension]
: getPrismModeFromExtension(extension);

return canUseDOM ? (
<SyntaxHighlighter
language={getPrismModeFromExtension(getFileExtensionLC(name))}
language={language}
style={vs}
className={
addComment && !this.props.onlyComments && !this.state.activeLine
Expand Down Expand Up @@ -234,6 +241,7 @@ SourceCodeViewer.propTypes = {
restrictCommentAuthor: PropTypes.string,
reviewClosed: PropTypes.bool,
onlyComments: PropTypes.bool,
highlightOverrides: PropTypes.object,
};

export default SourceCodeViewer;
4 changes: 4 additions & 0 deletions src/components/helpers/SourceCodeViewer/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export { default } from './SourceCodeViewer.js';
export { default as SourceCodeComment } from './SourceCodeComment.js';
export {
default as SourceCodeHighlightingSelector,
localStorageHighlightOverridesKey,
} from './SourceCodeHighlightingSelector.js';
Loading