client: improve markdown rendering
This commit is contained in:
parent
60f2ab99b3
commit
c55ca681b4
15 changed files with 285 additions and 259 deletions
|
@ -1,3 +1,10 @@
|
|||
.card {
|
||||
max-width: var(--main-content);
|
||||
margin: var(--gap) auto;
|
||||
padding: 2;
|
||||
border: 1px solid var(--light-gray);
|
||||
}
|
||||
|
||||
.input {
|
||||
background: #efefef;
|
||||
}
|
||||
|
|
|
@ -34,9 +34,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM
|
|||
|
||||
const newText = `${before}**${selectedText}**${after}`
|
||||
setText(newText)
|
||||
|
||||
// TODO; fails because settext async
|
||||
textareaRef.current.setSelectionRange(before.length + 2, before.length + 2 + selectedText.length)
|
||||
}
|
||||
}, [setText, textareaRef])
|
||||
|
||||
|
@ -50,8 +47,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM
|
|||
const selectedText = text.substring(selectionStart, selectionEnd)
|
||||
const newText = `${before}*${selectedText}*${after}`
|
||||
setText(newText)
|
||||
textareaRef.current.focus()
|
||||
textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length)
|
||||
}
|
||||
}, [setText, textareaRef])
|
||||
|
||||
|
@ -71,8 +66,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM
|
|||
}
|
||||
const newText = `${before}${formattedText}${after}`
|
||||
setText(newText)
|
||||
textareaRef.current.focus()
|
||||
textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length)
|
||||
}
|
||||
}, [setText, textareaRef])
|
||||
|
||||
|
@ -92,8 +85,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM
|
|||
}
|
||||
const newText = `${before}${formattedText}${after}`
|
||||
setText(newText)
|
||||
textareaRef.current.focus()
|
||||
textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length)
|
||||
}
|
||||
}, [setText, textareaRef])
|
||||
|
||||
|
|
|
@ -23,34 +23,6 @@ type Props = {
|
|||
remove?: () => void
|
||||
}
|
||||
|
||||
const DownloadButton = ({ rawLink }: { rawLink?: string }) => {
|
||||
return (<div className={styles.actionWrapper}>
|
||||
<ButtonGroup className={styles.actions}>
|
||||
<Tooltip text="Download">
|
||||
<a href={`${rawLink}?download=true`} target="_blank" rel="noopener noreferrer">
|
||||
<Button
|
||||
scale={2 / 3} px={0.6}
|
||||
icon={<Download />}
|
||||
auto
|
||||
aria-label="Download"
|
||||
/>
|
||||
</a>
|
||||
</Tooltip>
|
||||
<Tooltip text="Open raw in new tab">
|
||||
<a href={rawLink} target="_blank" rel="noopener noreferrer">
|
||||
<Button
|
||||
scale={2 / 3} px={0.6}
|
||||
icon={<ExternalLink />}
|
||||
auto
|
||||
aria-label="Open raw file in new tab"
|
||||
/>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
||||
const Document = ({ remove, title, content, setTitle, setContent, initialTab = 'edit', skeleton, handleOnContentChange }: Props) => {
|
||||
const codeEditorRef = useRef<HTMLTextAreaElement>(null)
|
||||
const [tab, setTab] = useState(initialTab)
|
||||
|
@ -98,7 +70,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = '
|
|||
return (
|
||||
<>
|
||||
<Spacer height={1} />
|
||||
<Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 'var(--main-content)', margin: "0 auto" }}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.fileNameContainer}>
|
||||
<Input
|
||||
placeholder="MyFile.md"
|
||||
|
@ -138,8 +110,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = '
|
|||
</Tabs>
|
||||
|
||||
</div >
|
||||
</Card >
|
||||
<Spacer height={1} />
|
||||
</div >
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => {
|
|||
fetchPost()
|
||||
}, [content, fileId, title])
|
||||
return (<>
|
||||
{isLoading ? <div>Loading...</div> : <article data-theme={theme} className={styles.markdownPreview} dangerouslySetInnerHTML={{ __html: preview }} style={{
|
||||
{isLoading ? <div>Loading...</div> : <article className={styles.markdownPreview} dangerouslySetInnerHTML={{ __html: preview }} style={{
|
||||
height
|
||||
}} />}
|
||||
</>)
|
||||
|
|
|
@ -86,3 +86,8 @@
|
|||
.markdownPreview code::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdownPreview img {
|
||||
max-width: 100%;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
.input {
|
||||
background: #efefef;
|
||||
.card {
|
||||
margin: var(--gap) auto;
|
||||
padding: var(--gap);
|
||||
border: 1px solid var(--light-gray);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.descriptionContainer {
|
||||
|
@ -26,10 +29,6 @@
|
|||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.actionWrapper {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
|
|
@ -84,7 +84,7 @@ const Document = ({ content, title, initialTab = 'edit', skeleton, id }: Props)
|
|||
return (
|
||||
<>
|
||||
<Spacer height={1} />
|
||||
<Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.fileNameContainer}>
|
||||
<Input
|
||||
value={title}
|
||||
|
@ -121,8 +121,7 @@ const Document = ({ content, title, initialTab = 'edit', skeleton, id }: Props)
|
|||
</Tabs>
|
||||
|
||||
</div >
|
||||
</Card >
|
||||
<Spacer height={1} />
|
||||
</div >
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
18
client/lib/hooks/use-debounce.ts
Normal file
18
client/lib/hooks/use-debounce.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
// useDebounce.js
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function useDebounce(value: any, delay: number) {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
|
@ -1,7 +1,18 @@
|
|||
import { marked } from 'marked'
|
||||
import { marked, Lexer } from 'marked'
|
||||
import Highlight, { defaultProps, Language, } from 'prism-react-renderer'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
|
||||
|
||||
|
||||
// image sizes. DDoS Safe?
|
||||
const imageSizeLink = /^!?\[((?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?(?:\s+=(?:[\w%]+)?x(?:[\w%]+)?)?)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/;
|
||||
//@ts-ignore
|
||||
Lexer.rules.inline.normal.link = imageSizeLink;
|
||||
//@ts-ignore
|
||||
Lexer.rules.inline.gfm.link = imageSizeLink;
|
||||
//@ts-ignore
|
||||
Lexer.rules.inline.breaks.link = imageSizeLink;
|
||||
|
||||
//@ts-ignore
|
||||
delete defaultProps.theme
|
||||
// import linkStyles from '../components/link/link.module.css'
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"cookie": "^0.4.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"marked": "^4.0.12",
|
||||
"next": "^12.1.1-canary.15",
|
||||
"postcss": "^8.4.12",
|
||||
|
|
|
@ -18,7 +18,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||
const skeletonHighlightColor = 'var(--lighter-gray)'
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-theme={theme}>
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
|
@ -39,7 +39,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||
<Component {...pageProps} />
|
||||
</SkeletonTheme>
|
||||
</GeistProvider>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,18 @@
|
|||
--keyword: #fff;
|
||||
--name: #fff;
|
||||
--highlight: #2e2e2e;
|
||||
|
||||
/* Dark Mode Colors */
|
||||
--bg: #131415;
|
||||
--fg: #fafbfc;
|
||||
--gray: #666;
|
||||
--light-gray: #444;
|
||||
--lighter-gray: #222;
|
||||
--lightest-gray: #1a1a1a;
|
||||
--article-color: #eaeaea;
|
||||
--header-bg: rgba(19, 20, 21, 0.45);
|
||||
--gray-alpha: rgba(255, 255, 255, 0.5);
|
||||
--selection: rgba(255, 255, 255, 0.99);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
|
@ -38,6 +50,17 @@
|
|||
--keyword: #000;
|
||||
--name: #333;
|
||||
--highlight: #eaeaea;
|
||||
|
||||
--bg: #fff;
|
||||
--fg: #000;
|
||||
--gray: #888;
|
||||
--light-gray: #dedede;
|
||||
--lighter-gray: #f5f5f5;
|
||||
--lightest-gray: #fafafa;
|
||||
--article-color: #212121;
|
||||
--header-bg: rgba(255, 255, 255, 0.8);
|
||||
--gray-alpha: rgba(19, 20, 21, 0.5);
|
||||
--selection: rgba(0, 0, 0, 0.99);
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
|
@ -10,11 +10,7 @@ article > * + * {
|
|||
|
||||
article img {
|
||||
max-width: 100%;
|
||||
/* width: var(--main-content); */
|
||||
width: auto;
|
||||
margin: auto;
|
||||
display: block;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
article [id]::before {
|
||||
|
|
|
@ -2278,6 +2278,11 @@ lodash.camelcase@^4.3.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
|
|
Loading…
Reference in a new issue