formatting

This commit is contained in:
Luck
2021-04-02 13:05:15 +01:00
parent 9701bd6529
commit 3fb11c5a9a
9 changed files with 188 additions and 115 deletions

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid",
"quoteProps": "consistent"
}

View File

@@ -17,8 +17,10 @@ async function loadFromBytebin(id) {
const resp = await fetch('https://bytebin.lucko.me/' + id); const resp = await fetch('https://bytebin.lucko.me/' + id);
if (resp.ok) { if (resp.ok) {
const content = await resp.text(); const content = await resp.text();
const type = parseLanguageFromContentType(resp.headers.get('content-type')); const type = parseLanguageFromContentType(
resp.headers.get('content-type')
);
document.title = 'paste | ' + id; document.title = 'paste | ' + id;
return { ok: true, content, type }; return { ok: true, content, type };
} else { } else {
@@ -52,7 +54,7 @@ export default function App() {
useEffect(() => { useEffect(() => {
if (pasteId && state === INITIAL) { if (pasteId && state === INITIAL) {
setState(LOADING); setState(LOADING);
setContent('Loading...') setContent('Loading...');
loadFromBytebin(pasteId).then(({ ok, content, type }) => { loadFromBytebin(pasteId).then(({ ok, content, type }) => {
if (ok) { if (ok) {
setContent(content); setContent(content);
@@ -63,11 +65,17 @@ export default function App() {
setContent(get404Message(pasteId)); setContent(get404Message(pasteId));
} }
setState(LOADED); setState(LOADED);
}) });
} }
}, [pasteId, state, setContent]) }, [pasteId, state, setContent]);
return <Editor content={content} setContent={setContent} contentType={contentType} />; return (
<Editor
content={content}
setContent={setContent}
contentType={contentType}
/>
);
} }
function get404Message(pasteId) { function get404Message(pasteId) {
@@ -82,4 +90,4 @@ function get404Message(pasteId) {
not found: '${pasteId}' not found: '${pasteId}'
maybe the paste expired? maybe the paste expired?
`; `;
} }

View File

@@ -8,38 +8,16 @@ import themes from '../style/themes';
export default function Editor({ content, setContent, contentType }) { export default function Editor({ content, setContent, contentType }) {
const [language, setLanguage] = useState('plain'); const [language, setLanguage] = useState('plain');
const [theme, setTheme] = useState(() => { const [theme, setTheme] = usePreference(
const preference = ls.get('theme'); 'theme',
if (preference && themes[preference]) { 'dark',
return preference; pref => !!themes[pref]
} else { );
return 'dark'; const [fontSize, setFontSize, fontSizeCheck] = usePreference(
} 'fontsize',
}); 16,
const [fontSize, setFontSize] = useState(() => { pref => pref >= 10 && pref <= 22
const preference = ls.get('fontsize'); );
if (preference && preference >= 10 && preference <= 22) {
return preference;
} else {
return 16;
}
});
useEffect(() => {
if (theme === 'dark') {
ls.remove('theme');
} else {
ls.set('theme', theme);
}
}, [theme]);
useEffect(() => {
if (fontSize === 16) {
ls.remove('fontsize');
} else {
ls.set('fontsize', fontSize);
}
}, [fontSize]);
useEffect(() => { useEffect(() => {
if (contentType) { if (contentType) {
@@ -48,21 +26,53 @@ export default function Editor({ content, setContent, contentType }) {
}, [contentType]); }, [contentType]);
function zoom(delta) { function zoom(delta) {
const result = fontSize + delta; const newFontSize = fontSize + delta;
if (result && result >= 10 && result <= 22) { if (fontSizeCheck(newFontSize)) {
setFontSize(result); setFontSize(newFontSize);
} }
} }
return <> return (
<ThemeProvider theme={themes[theme]}> <>
<EditorControls <ThemeProvider theme={themes[theme]}>
code={content} setCode={setContent} <EditorControls
language={language} setLanguage={setLanguage} code={content}
theme={theme} setTheme={setTheme} setCode={setContent}
zoom={zoom} language={language}
/> setLanguage={setLanguage}
<EditorTextArea code={content} setCode={setContent} language={language} fontSize={fontSize} /> theme={theme}
</ThemeProvider> setTheme={setTheme}
</> zoom={zoom}
/>
<EditorTextArea
code={content}
setCode={setContent}
language={language}
fontSize={fontSize}
/>
</ThemeProvider>
</>
);
}
// hook used to load "preference" settings from local storage, or fall back to a default value.
function usePreference(id, defaultValue, valid) {
const [value, setValue] = useState(() => {
const pref = ls.get(id);
if (pref && valid(pref)) {
return pref;
} else {
return defaultValue;
}
});
useEffect(() => {
if (value === defaultValue) {
ls.remove(id);
} else {
ls.set(id, value);
}
}, [value, id, defaultValue]);
return [value, setValue, valid];
} }

View File

@@ -8,24 +8,32 @@ import { MenuButton, Button } from './Menu';
import { languageIds } from '../highlighting'; import { languageIds } from '../highlighting';
import themes from '../style/themes'; import themes from '../style/themes';
export default function EditorControls({ code, setCode, language, setLanguage, theme, setTheme, zoom }) { export default function EditorControls({
code,
setCode,
language,
setLanguage,
theme,
setTheme,
zoom,
}) {
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [recentlySaved, setRecentlySaved] = useState(false); const [recentlySaved, setRecentlySaved] = useState(false);
useEffect(() => { useEffect(() => {
setRecentlySaved(false); setRecentlySaved(false);
}, [code, language]) }, [code, language]);
const save = useCallback(() => { const save = useCallback(() => {
if (!code || recentlySaved) { if (!code || recentlySaved) {
return; return;
} }
setSaving(true); setSaving(true);
saveToBytebin(code, language).then((pasteId) => { saveToBytebin(code, language).then(pasteId => {
setSaving(false); setSaving(false);
setRecentlySaved(true); setRecentlySaved(true);
history.replace({ history.replace({
pathname: pasteId pathname: pasteId,
}); });
copy(window.location.href); copy(window.location.href);
document.title = 'paste | ' + pasteId; document.title = 'paste | ' + pasteId;
@@ -33,8 +41,8 @@ export default function EditorControls({ code, setCode, language, setLanguage, t
}, [code, language, recentlySaved]); }, [code, language, recentlySaved]);
useEffect(() => { useEffect(() => {
const listener = (e) => { const listener = e => {
if ((e.ctrlKey || e.metaKey)) { if (e.ctrlKey || e.metaKey) {
if (e.key === 's' || e.key === 'S') { if (e.key === 's' || e.key === 'S') {
e.preventDefault(); e.preventDefault();
save(); save();
@@ -45,8 +53,8 @@ export default function EditorControls({ code, setCode, language, setLanguage, t
zoom(e.key === '=' ? 1 : -1); zoom(e.key === '=' ? 1 : -1);
} }
} }
} };
window.addEventListener('keydown', listener); window.addEventListener('keydown', listener);
return () => window.removeEventListener('keydown', listener); return () => window.removeEventListener('keydown', listener);
}, [save, zoom]); }, [save, zoom]);
@@ -55,7 +63,7 @@ export default function EditorControls({ code, setCode, language, setLanguage, t
setCode(''); setCode('');
setLanguage('plain'); setLanguage('plain');
history.replace({ history.replace({
pathname: '/' pathname: '/',
}); });
document.title = 'paste'; document.title = 'paste';
} }
@@ -65,21 +73,36 @@ export default function EditorControls({ code, setCode, language, setLanguage, t
<Section> <Section>
<Button onClick={reset}>[new]</Button> <Button onClick={reset}>[new]</Button>
<Button onClick={save}> <Button onClick={save}>
{recentlySaved {recentlySaved ? '[link copied!]' : saving ? '[saving...]' : '[save]'}
? '[link copied!]'
: saving ? '[saving...]' : '[save]'
}
</Button> </Button>
<MenuButton label="language" value={language} setValue={setLanguage} ids={languageIds} /> <MenuButton
label="language"
value={language}
setValue={setLanguage}
ids={languageIds}
/>
</Section> </Section>
<Section> <Section>
<Button onClick={() => zoom(1)}>[+ </Button> <Button onClick={() => zoom(1)}>[+ </Button>
<Button onClick={() => zoom(-1)}> -]</Button> <Button onClick={() => zoom(-1)}> -]</Button>
<MenuButton label="theme" value={theme} setValue={setTheme} ids={Object.keys(themes)} /> <MenuButton
<Button className="optional" as="a" href="https://github.com/lucko/paste" target="_blank" rel="noreferrer">[about]</Button> label="theme"
value={theme}
setValue={setTheme}
ids={Object.keys(themes)}
/>
<Button
className="optional"
as="a"
href="https://github.com/lucko/paste"
target="_blank"
rel="noreferrer"
>
[about]
</Button>
</Section> </Section>
</Header> </Header>
) );
} }
const Header = styled.header` const Header = styled.header`
@@ -123,9 +146,9 @@ async function saveToBytebin(code, language) {
headers: { headers: {
'Content-Type': contentType, 'Content-Type': contentType,
'Content-Encoding': 'gzip', 'Content-Encoding': 'gzip',
'Accept': 'application/json' 'Accept': 'application/json',
}, },
body: compressed body: compressed,
}); });
if (resp.ok) { if (resp.ok) {

View File

@@ -1,7 +1,7 @@
import styled from 'styled-components'; import styled from 'styled-components';
export default function EditorPrismStyle({ children }) { export default function EditorPrismStyle({ children }) {
return <Main>{children}</Main> return <Main>{children}</Main>;
} }
const Main = styled.main` const Main = styled.main`
@@ -9,8 +9,8 @@ const Main = styled.main`
color: ${props => props.theme.editor.primary}; color: ${props => props.theme.editor.primary};
background: ${props => props.theme.editor.background}; background: ${props => props.theme.editor.background};
code[class*="language-"], code[class*='language-'],
pre[class*="language-"] { pre[class*='language-'] {
text-shadow: 0 1px white; text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em; font-size: 1em;
@@ -31,29 +31,33 @@ const Main = styled.main`
hyphens: none; hyphens: none;
} }
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, pre[class*='language-']::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { pre[class*='language-'] ::-moz-selection,
code[class*='language-']::-moz-selection,
code[class*='language-'] ::-moz-selection {
text-shadow: none; text-shadow: none;
background: ${props => props.theme.editor.selection}; background: ${props => props.theme.editor.selection};
} }
pre[class*="language-"]::selection, pre[class*="language-"] ::selection, pre[class*='language-']::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection { pre[class*='language-'] ::selection,
code[class*='language-']::selection,
code[class*='language-'] ::selection {
text-shadow: none; text-shadow: none;
background: ${props => props.theme.editor.selection}; background: ${props => props.theme.editor.selection};
} }
@media print { @media print {
code[class*="language-"], code[class*='language-'],
pre[class*="language-"] { pre[class*='language-'] {
text-shadow: none; text-shadow: none;
} }
} }
/* Code blocks */ /* Code blocks */
pre[class*="language-"] { pre[class*='language-'] {
padding: 1em; padding: 1em;
margin: .5em 0; margin: 0.5em 0;
overflow: auto; overflow: auto;
} }
@@ -84,7 +88,7 @@ const Main = styled.main`
.token.boolean { .token.boolean {
color: ${props => props.theme.editor.keyword}; color: ${props => props.theme.editor.keyword};
} }
.token.constant { .token.constant {
color: ${props => props.theme.editor.constant}; color: ${props => props.theme.editor.constant};
} }
@@ -152,4 +156,4 @@ const Main = styled.main`
.token.entity { .token.entity {
cursor: help; cursor: help;
} }
`; `;

View File

@@ -29,12 +29,12 @@ export default function EditorTextArea({ code, setCode, language, fontSize }) {
placeholder={'Paste (or type) some code...'} placeholder={'Paste (or type) some code...'}
padding={10} padding={10}
size={fontSize} size={fontSize}
textareaId='code-area' textareaId="code-area"
autoFocus={true} autoFocus={true}
onKeyDown={keydown} onKeyDown={keydown}
/> />
</EditorPrismStyle> </EditorPrismStyle>
) );
} }
const StyledReactEditor = styled(ReactEditor)` const StyledReactEditor = styled(ReactEditor)`
@@ -101,29 +101,43 @@ function handleKeydown(e, editor) {
if (pair) { if (pair) {
e.preventDefault(); e.preventDefault();
editor._applyEdits({ editor._applyEdits({
value: value.substring(0, selectionStart) + pair[0] + pair[1] + value.substring(selectionEnd), value:
value.substring(0, selectionStart) +
pair[0] +
pair[1] +
value.substring(selectionEnd),
selectionStart: selectionStart + 1, selectionStart: selectionStart + 1,
selectionEnd: selectionStart + 1 selectionEnd: selectionStart + 1,
}); });
} }
// When pressing enter immediately after an open bracket, automatically add a newline plus extra indent // When pressing enter immediately after an open bracket, automatically add a newline plus extra indent
if (e.keyCode === KEYCODE_ENTER && selectionEnd !== 0 && value[selectionEnd - 1] === '{') { if (
e.keyCode === KEYCODE_ENTER &&
selectionEnd !== 0 &&
value[selectionEnd - 1] === '{'
) {
const line = editor._getLines(value, selectionStart).pop(); const line = editor._getLines(value, selectionStart).pop();
const matches = line.match(/^\s+/); const matches = line.match(/^\s+/);
const existingIndent = (matches ? matches[0] : ''); const existingIndent = matches ? matches[0] : '';
const indent = ' '; const indent = ' ';
const updatedValue = value.substring(0, selectionStart) + const updatedValue =
'\n' + existingIndent + indent + value.substring(0, selectionStart) +
'\n' + existingIndent + value.substring(selectionEnd); '\n' +
const updatedSelection = selectionStart + 1 /* newline */ + existingIndent.length + indent.length; existingIndent +
indent +
'\n' +
existingIndent +
value.substring(selectionEnd);
const updatedSelection =
selectionStart + 1 /* newline */ + existingIndent.length + indent.length;
e.preventDefault(); e.preventDefault();
editor._applyEdits({ editor._applyEdits({
value: updatedValue, value: updatedValue,
selectionStart: updatedSelection, selectionStart: updatedSelection,
selectionEnd: updatedSelection selectionEnd: updatedSelection,
}) });
} }
} }

View File

@@ -6,7 +6,7 @@ export const Button = styled.div`
height: 100%; height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 .25em; padding: 0 0.25em;
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
@@ -32,7 +32,7 @@ const Menu = styled.ul`
overflow: auto; overflow: auto;
> li { > li {
padding: .15em .5em; padding: 0.15em 0.5em;
} }
> li:hover { > li:hover {
@@ -48,7 +48,7 @@ export const MenuButton = ({ label, ids, value, setValue }) => {
return; return;
} }
const listener = (e) => setOpen(false); const listener = e => setOpen(false);
window.addEventListener('click', listener); window.addEventListener('click', listener);
return () => window.removeEventListener('click', listener); return () => window.removeEventListener('click', listener);
}, [open, setOpen]); }, [open, setOpen]);
@@ -65,12 +65,17 @@ export const MenuButton = ({ label, ids, value, setValue }) => {
return ( return (
<Button onClick={toggleOpen}> <Button onClick={toggleOpen}>
[<span>{label}: </span>{value}] [<span>{label}: </span>
{value}]
{open && ( {open && (
<Menu> <Menu>
{ids.map(id => <li key={id} onClick={e => select(e, id)}>{id}</li>)} {ids.map(id => (
<li key={id} onClick={e => select(e, id)}>
{id}
</li>
))}
</Menu> </Menu>
)} )}
</Button> </Button>
) );
} };

View File

@@ -22,8 +22,8 @@ const themes = {
keyword: '#07a', keyword: '#07a',
function: '#DD4A68', function: '#DD4A68',
className: '#DD4A68', className: '#DD4A68',
variable: '#e90' variable: '#e90',
} },
}, },
dark: { dark: {
primary: '#022550', primary: '#022550',
@@ -48,9 +48,9 @@ const themes = {
keyword: '#1CCBEF', keyword: '#1CCBEF',
function: '#BCBCBC', function: '#BCBCBC',
className: '#82CF75', className: '#82CF75',
variable: '#ee9900' variable: '#ee9900',
} },
} },
}; };
export default themes; export default themes;

View File

@@ -18,7 +18,7 @@ import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-json'; import 'prismjs/components/prism-json';
import 'prismjs/components/prism-kotlin'; import 'prismjs/components/prism-kotlin';
//import 'prismjs/components/prism-log'; //import 'prismjs/components/prism-log';
import './prism/prism-log'; // TODO replace with above once released import '../prism/prism-log'; // TODO replace with above once released
import 'prismjs/components/prism-markdown'; import 'prismjs/components/prism-markdown';
//import 'prismjs/components/prism-php'; //import 'prismjs/components/prism-php';
import 'prismjs/components/prism-protobuf'; import 'prismjs/components/prism-protobuf';
@@ -58,10 +58,10 @@ export const languageIds = [
'rust', 'rust',
'sql', 'sql',
'toml', 'toml',
'yaml' 'yaml',
] ];
export function getHighlighter(language) { export function getHighlighter(language) {
const grammar = languages[language] || {}; const grammar = languages[language] || {};
return (input) => highlight(input, grammar); return input => highlight(input, grammar);
} }