Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added Prism React Renderer for syntax highlighting #89

Closed
wants to merge 10 commits into from
217 changes: 189 additions & 28 deletions packages/web/components/CodeFile.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useRef } from "react";
import * as Prism from "prismjs";
import "prismjs/themes/prism.css";
import { useState, useEffect } from "react";
//import * as Prism from "prismjs";
//import "prismjs/themes/prism.css";
import "prismjs/themes/prism-coy.css";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
import "prismjs/plugins/line-numbers/prism-line-numbers.js";
import "prismjs/plugins/line-highlight/prism-line-highlight.css";
import "prismjs/plugins/line-highlight/prism-line-highlight.js";

import { FindCodeReviewQuestionsComponent } from "./apollo-components";
import styled, { css } from "styled-components";
import Highlight, { defaultProps, Language } from "prism-react-renderer";
//import theme from "prism-react-renderer/themes/vsDarkPlus";

import {
FindCodeReviewQuestionsComponent,
FindCodeReviewQuestionsQuery,
} from "./apollo-components";
import { filenameToLang } from "../utils/filenameToLang";
import { loadLanguage } from "../utils/loadLanguage";
import { QuestionSection } from "./QuestionSection";

interface Props {
Expand All @@ -18,49 +20,208 @@ interface Props {
postId: string;
}

/*
* *Styles for the line numbers coming from the server
*
* TODO: Perhaps refactor SelectLinesMouse as a 'sub function' of SelectLines?
* Or the two in a more general utils?
*/
const SelectLines = (prop: FindCodeReviewQuestionsQuery) => {
const styles = prop.findCodeReviewQuestions.reduce((total, current) => {
return (total += `
& .token-line:nth-child(n+${current.startingLineNum}):nth-child(-n+${
current.endingLineNum
}) {
background-color: #ffffcc;
}
`);
}, "");
return css`
${styles}
`;
};

/*
*Styles for the onClick line numbers
*
* TODO: Perhaps refactor SelectLinesMouse as a 'sub function' of SelectLines?
* Or the two in a more general utils?
*/
const SelectLinesMouse = (arg: number[]) => {
// establishing defaults
// The lenght of the args array can be variable
const startLine = arg[0] || 0;
const endLine = arg[1] || startLine;

const styles = `
& .token-line:nth-child(n+${startLine}):nth-child(-n+${endLine}) {
background-color: #ffddbb;
}
`;
return css`
${styles}
`;
};

export const CodeFile: React.SFC<Props> = ({ code, path, postId }) => {
const hasLoadedLanguage = useRef(false);
// does this needs so many states? Should be simplified more...
const [lineSelectionState, setLineSelectionState] = useState<number[]>([
0,
0,
]);
const [startLinesSelection, setStartLinesSelection] = useState<number>(0);
const [endLinesSelection, setEndLinesSelection] = useState<number>(0);

const lang = path ? filenameToLang(path) : "";
const lang: Language = path ? filenameToLang(path) : "";
const variables = {
path,
postId,
};

/*
* Handler to manage the array of selected lines
* It simulates the github line number selection
* */
const handleSelectLine = (lineNumber: number) => {
let tempSelectionState = lineSelectionState.filter(value => {
return value !== 0;
});

const withoutRepeatLines = tempSelectionState.filter(value => {
return value !== lineNumber;
});

// so many else/if are not so legible...
// a switch might be possible here, but does it bring more or less?
if (tempSelectionState.length == 0) {
tempSelectionState = [lineNumber, lineNumber];
} else if (withoutRepeatLines.length == 0) {
tempSelectionState = [0, 0];
} else if (tempSelectionState.length !== withoutRepeatLines.length) {
tempSelectionState = [withoutRepeatLines[0], withoutRepeatLines[0]];
} else if (
tempSelectionState.length == 2 ||
tempSelectionState.length == 1
) {
tempSelectionState[1] = lineNumber;
}
// The react hook must be outside conditions?
tempSelectionState.sort((a, b) => a - b);
setStartLinesSelection(tempSelectionState[0] || 0);
setEndLinesSelection(tempSelectionState[1] || 0);
setLineSelectionState([...tempSelectionState]);
return;
};

/*
* handleStartLinesSelection and handleEndLinesSelection
* are still a little buggy, but almost there
* TODO: merge the 2 functions to avoid repetition
* TODO: better handling of the situation when values of input1 > input2
* TODO: ability to reset the line selections directly from the input
*/
const handleStartLinesSelection = (event: any) => {
if (event.target) {
// || lineSelectionState[0] to block NaN and secure default
// TODO: add a better checks
let value1 = parseInt(event.target.value, 10) || lineSelectionState[0];
let value2 = lineSelectionState[1];
if (value1 > value2) {
value2 = value1;
}
setLineSelectionState([value1, value2]);
setStartLinesSelection(value1);
setEndLinesSelection(value2);
}
};

const handleEndLinesSelection = (event: any) => {
if (event.target) {
// || lineSelectionState[0] to block NaN and secure default
// TODO: add a better checks
let value = parseInt(event.target.value, 10) || lineSelectionState[1];
value = value >= lineSelectionState[0] ? value : lineSelectionState[0];
setLineSelectionState([lineSelectionState[0], value]);
setEndLinesSelection(value);
}
};

return (
<FindCodeReviewQuestionsComponent variables={variables}>
{({ data, loading }) => {
if (!data || loading) {
return null;
}

const dataLines = data.findCodeReviewQuestions.map(q => {
return `${q.startingLineNum}-${q.endingLineNum}`;
});
const Pre = styled.pre`
text-align: left;
margin: 4em 0;
padding: 0.5em;

& .token-line {
line-height: 1.3em;
height: 1.3em;
}

/* Style for the effect of alternating colors in the background */
& .token-line:nth-child(odd) {
background: #f3faff;
}

${SelectLines(data)};

${SelectLinesMouse(lineSelectionState)};
`;

/* Style for the line numbers */
const LineNo = styled.span`
display: inline-block;
width: 2em;
user-select: none;
opacity: 0.3;
&:hover {
font-weight: 900;
opacity: 0.4;
cursor: pointer;
}
`;

return (
<>
<pre
ref={async () => {
if (!hasLoadedLanguage.current) {
try {
await loadLanguage(lang);
} catch {}
Prism.highlightAll();
hasLoadedLanguage.current = true;
}
}}
className="line-numbers"
data-line={dataLines.join(" ")}
<Highlight
{...defaultProps}
theme={undefined}
code={code}
language={lang}
>
<code className={`language-${lang}`}>{code}</code>
</pre>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<Pre className={className} style={style}>
{tokens.map((line, i) => {
return (
<div {...getLineProps({ line, key: i })}>
<LineNo onClick={() => handleSelectLine(i + 1)}>
{i + 1}
</LineNo>
{line.map((token, key) => {
return <span {...getTokenProps({ token, key })} />;
})}
</div>
);
})}
</Pre>
)}
</Highlight>
<QuestionSection
variables={variables}
code={code || ""}
postId={postId}
programmingLanguage={lang}
path={path}
/* Added a props to pass and handle the selected lines */
startLinesSelection={startLinesSelection}
endLinesSelection={endLinesSelection}
handleStartLinesSelection={handleStartLinesSelection}
handleEndLinesSelection={handleEndLinesSelection}
/>
</>
);
Expand Down
38 changes: 25 additions & 13 deletions packages/web/components/QuestionForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import * as React from "react";
import { ChangeEvent } from "react";

import { DebounceInput } from "react-debounce-input";

import { CreateCodeReviewQuestionComponent } from "./apollo-components";
import { useInputValue } from "../utils/useInputValue";
Expand All @@ -8,16 +11,22 @@ export interface QuestionFormProps {
path?: string;
postId: string;
programmingLanguage?: string;
startLinesSelection: number;
endLinesSelection: number;
handleStartLinesSelection: (event: ChangeEvent<HTMLInputElement>) => void;
handleEndLinesSelection: (event: ChangeEvent<HTMLInputElement>) => void;
}

export const QuestionForm = ({
code,
path,
postId,
programmingLanguage,
startLinesSelection,
endLinesSelection,
handleStartLinesSelection,
handleEndLinesSelection,
}: QuestionFormProps) => {
const [startingLineNum, startingLineNumChange] = useInputValue("0");
const [endingLineNum, endingLineNumChange] = useInputValue("0");
const [text, textChange] = useInputValue("");

return (
Expand All @@ -26,8 +35,8 @@ export const QuestionForm = ({
<form
onSubmit={async e => {
e.preventDefault();
const start = parseInt(startingLineNum, 10);
const end = parseInt(endingLineNum, 10);
const start = startLinesSelection;
const end = endLinesSelection;
const response = await mutate({
variables: {
codeReviewQuestion: {
Expand All @@ -47,20 +56,23 @@ export const QuestionForm = ({
},
});

console.log(response);
//console.log(response);
}}
>
<input
{/* see https://www.npmjs.com/package/react-debounce-input */}
<DebounceInput
name="startingLineNum"
placeholder="startingLineNum"
value={startingLineNum}
onChange={startingLineNumChange}
placeholder="0"
value={startLinesSelection}
debounceTimeout={300}
onChange={handleStartLinesSelection}
/>
<input
<DebounceInput
name="endingLineNum"
placeholder="endingLineNum"
value={endingLineNum}
onChange={endingLineNumChange}
placeholder="0"
value={endLinesSelection}
debounceTimeout={300}
onChange={handleEndLinesSelection}
/>
<input
name="question"
Expand Down
13 changes: 13 additions & 0 deletions packages/web/components/QuestionSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import {
} from "./apollo-components";
import { QuestionForm, QuestionFormProps } from "./QuestionForm";
import { Question } from "./Question";
import { ChangeEvent } from "react";

interface Props extends QuestionFormProps {
variables: FindCodeReviewQuestionsVariables;
startLinesSelection: number;
endLinesSelection: number;
handleStartLinesSelection: (event: ChangeEvent<HTMLInputElement>) => void;
handleEndLinesSelection: (event: ChangeEvent<HTMLInputElement>) => void;
}

export const QuestionSection = ({
Expand All @@ -16,6 +21,10 @@ export const QuestionSection = ({
postId,
path,
programmingLanguage,
startLinesSelection,
endLinesSelection,
handleStartLinesSelection,
handleEndLinesSelection,
}: Props) => {
return (
<FindCodeReviewQuestionsComponent variables={variables}>
Expand All @@ -31,6 +40,10 @@ export const QuestionSection = ({
postId={postId}
path={path}
programmingLanguage={programmingLanguage}
startLinesSelection={startLinesSelection}
endLinesSelection={endLinesSelection}
handleStartLinesSelection={handleStartLinesSelection}
handleEndLinesSelection={handleEndLinesSelection}
/>
<div>
{data.findCodeReviewQuestions.map(crq => (
Expand Down
2 changes: 2 additions & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
"next": "7.0.2",
"next-routes": "1.4.2",
"prismjs": "1.15.0",
"prism-react-renderer": "0.1.5",
"react": "16.7.0-alpha.2",
"react-apollo": "2.3.3",
"react-debounce-input": "3.2.0",
"react-dom": "16.7.0-alpha.2",
"styled-components": "4.1.3"
},
Expand Down
Loading