Files
lucko-paste/src/components/EditorTextArea.js

232 lines
5.6 KiB
JavaScript
Raw Normal View History

2022-01-08 19:10:27 +00:00
import React, { useCallback, useEffect, useRef, useState } from 'react';
import history from 'history/browser';
2022-01-08 19:10:27 +00:00
import Editor from '@monaco-editor/react';
import styled from 'styled-components';
import themes, { makeMonacoTheme } from '../style/themes';
export default function EditorTextArea({
forcedContent,
2022-01-08 19:19:16 +00:00
actualContent,
2022-01-08 19:10:27 +00:00
setActualContent,
theme,
language,
fontSize,
}) {
const [editor, setEditor] = useState();
const [monaco, setMonaco] = useState();
const [selected, toggleSelected] = useSelectedLine();
const editorAreaRef = useRef();
2022-01-08 22:59:01 +00:00
useLineNumberMagic(
editorAreaRef,
selected,
toggleSelected,
forcedContent,
editor,
monaco
);
2022-01-08 19:19:16 +00:00
usePlaceholderText(actualContent, editor, monaco);
2022-01-08 19:10:27 +00:00
function beforeMount(monaco) {
for (const [id, theme] of Object.entries(themes)) {
monaco.editor.defineTheme(id, makeMonacoTheme(theme));
2022-01-08 22:59:01 +00:00
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
noSyntaxValidation: true,
});
2022-01-08 19:10:27 +00:00
}
2021-03-26 22:00:12 +00:00
}
2022-01-08 19:10:27 +00:00
function onMount(editor, monaco) {
setEditor(editor);
setMonaco(monaco);
editor.focus();
2021-03-28 17:39:16 +01:00
}
2022-01-08 19:10:27 +00:00
const onChange = useCallback(
value => {
setActualContent(value);
},
[setActualContent]
);
2022-01-09 20:37:39 +00:00
// detect indentation whenever new forced content is set
useEffect(() => {
if (!editor) {
return;
}
editor.getModel().detectIndentation(true, 2);
}, [editor, forcedContent])
2021-03-26 22:00:12 +00:00
return (
2022-01-08 19:10:27 +00:00
<EditorArea ref={editorAreaRef}>
<Editor
theme={theme.id}
language={language === 'plain' ? 'plaintext' : language}
options={{
fontFamily: 'JetBrains Mono',
fontSize: fontSize,
fontLigatures: true,
wordWrap: true,
renderLineHighlight: 'none',
2022-01-08 22:59:01 +00:00
renderValidationDecorations: false,
2022-01-08 19:10:27 +00:00
}}
beforeMount={beforeMount}
onMount={onMount}
onChange={onChange}
value={forcedContent}
2021-03-26 22:00:12 +00:00
/>
2022-01-08 19:10:27 +00:00
</EditorArea>
2021-04-02 13:05:15 +01:00
);
2021-03-26 22:00:12 +00:00
}
2021-03-27 11:49:46 +00:00
2022-01-08 19:10:27 +00:00
const EditorArea = styled.main`
margin-top: 2.5em;
height: 100%;
2022-01-08 19:10:27 +00:00
.line-numbers {
cursor: pointer !important;
2021-05-21 11:56:05 +01:00
}
2022-01-08 19:10:27 +00:00
.highlighted-line + div {
color: ${props => props.theme.editor.lineNumberHl};
background-color: ${props => props.theme.editor.lineNumberHlBackground};
font-weight: bold;
}
2022-01-08 19:19:16 +00:00
.placeholder-text {
position: absolute;
opacity: 0.5;
font-weight: lighter;
&::after {
content: 'Paste (or type) some code...';
}
}
2022-01-08 19:10:27 +00:00
`;
2021-05-21 11:56:05 +01:00
2022-01-08 19:19:16 +00:00
function usePlaceholderText(actualContent, editor, monaco) {
useEffect(() => {
if (!editor || !monaco || actualContent) {
return;
}
const decoration = {
range: new monaco.Range(1, 1, 1, 1),
options: {
after: {
content: '\u200B',
inlineClassName: 'placeholder-text',
},
},
};
const decorations = editor.deltaDecorations([], [decoration]);
return () => {
editor.deltaDecorations(decorations, []);
};
}, [editor, monaco, actualContent]);
}
2022-01-08 19:10:27 +00:00
function useLineNumberMagic(
editorAreaRef,
selected,
toggleSelected,
2022-01-08 22:01:01 +00:00
forcedContent,
2022-01-08 19:10:27 +00:00
editor,
monaco
) {
// add an event listener for clicking on line numbers
useEffect(() => {
const node = editorAreaRef.current;
if (!node) {
return;
}
2021-03-27 11:49:46 +00:00
2022-01-08 19:10:27 +00:00
const handler = click => {
const target = click?.target;
if (target && target.classList.contains('line-numbers')) {
toggleSelected(parseInt(target.textContent), click);
}
};
2021-03-27 11:49:46 +00:00
2022-01-08 19:10:27 +00:00
node.addEventListener('click', handler);
return () => node.removeEventListener('click', handler);
}, [editorAreaRef, toggleSelected]);
2021-03-27 11:49:46 +00:00
2022-01-08 19:10:27 +00:00
// apply a 'highlighed' decoration to the selected lines
2022-01-08 22:01:01 +00:00
// and scroll them into view if not already
2022-01-08 19:10:27 +00:00
useEffect(() => {
2022-01-08 22:01:01 +00:00
if (!editor || !monaco || selected[0] === -1) {
2022-01-08 19:10:27 +00:00
return;
}
2021-05-21 11:56:05 +01:00
2022-01-08 22:01:01 +00:00
// apply a decoration
2022-01-08 22:59:01 +00:00
const newDecorations = [
{
range: new monaco.Range(selected[0], 1, selected[1], 1),
options: {
isWholeLine: true,
linesDecorationsClassName: 'highlighted-line',
},
2022-01-08 22:01:01 +00:00
},
2022-01-08 22:59:01 +00:00
];
2022-01-08 19:19:16 +00:00
const decorations = editor.deltaDecorations([], newDecorations);
2021-03-28 17:39:16 +01:00
2022-01-08 22:01:01 +00:00
// scroll the selected line into view
if (selected[0] === selected[1]) {
editor.revealLineInCenterIfOutsideViewport(selected[0]);
} else {
editor.revealLinesInCenterIfOutsideViewport(selected[0], selected[1]);
}
// cleanup by removing the decorations
2022-01-08 19:10:27 +00:00
return () => {
editor.deltaDecorations(decorations, []);
};
2022-01-08 22:01:01 +00:00
}, [editor, monaco, selected, forcedContent]);
2022-01-08 19:10:27 +00:00
}
2021-05-21 11:56:05 +01:00
function useSelectedLine() {
// extract highlighted lines from window hash
const [selected, setSelected] = useState(() => {
const hash = window.location.hash;
if (/^#L\d+(-\d+)?$/.test(hash)) {
const [start, end] = hash.substring(2).split('-').map(Number);
2022-01-08 19:10:27 +00:00
return [start, isNaN(end) ? start : end];
2021-05-21 11:56:05 +01:00
} else {
return [-1, -1];
}
});
// update window hash when a new line is highlighted
useEffect(() => {
let hash = '';
2021-05-21 11:56:05 +01:00
if (selected[0] !== -1) {
2022-01-08 19:10:27 +00:00
if (selected[1] !== selected[0]) {
2021-05-21 13:57:36 +01:00
const start = Math.min(...selected);
const end = Math.max(...selected);
hash = `#L${start}-${end}`;
2021-05-21 11:56:05 +01:00
} else {
hash = `#L${selected[0]}`;
2021-05-21 11:56:05 +01:00
}
}
history.replace({ hash });
2021-05-21 11:56:05 +01:00
}, [selected]);
// toggle the highlighting for a given line
2022-01-08 19:10:27 +00:00
function toggleSelected(lineNo, e) {
const shift = e.shiftKey;
if (selected[0] === lineNo && selected[1] === lineNo) {
2021-05-21 11:56:05 +01:00
setSelected([-1, -1]);
} else if (selected[0] === -1 || !shift) {
2022-01-08 19:10:27 +00:00
setSelected([lineNo, lineNo]);
2021-05-21 11:56:05 +01:00
} else {
setSelected([selected[0], lineNo]);
}
}
2022-01-08 19:10:27 +00:00
return [selected, toggleSelected];
2021-03-28 17:39:16 +01:00
}