mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-21 18:02:32 +01:00
markdown v2
This commit is contained in:
parent
1ab749a762
commit
9afc5b118d
13
package.json
13
package.json
@ -49,15 +49,12 @@
|
||||
"react-select-search": "^4.1.6",
|
||||
"react-spinners": "^0.13.8",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-use-error-boundary": "^3.0.0",
|
||||
"rehype-prism": "^2.2.2",
|
||||
"rehype-react": "^8.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"reoverlay": "^1.0.3",
|
||||
"styled-components": "^5.3.10",
|
||||
"unified": "^11.0.3"
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
@ -65,9 +62,9 @@
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/loadable__component": "^5.13.5",
|
||||
"@types/node": "^16.18.50",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react": "^18.2.22",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-syntax-highlighter": "^15.5.7",
|
||||
"@types/styled-components": "^5.1.27",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"@typescript-eslint/parser": "^6.7.0",
|
||||
|
895
pnpm-lock.yaml
895
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,11 @@ const Wrapper = styled(Container)<{ size: number }>`
|
||||
height: ${(props) => props.size}px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Suspense, lazy } from "react";
|
||||
import { withErrorBoundary } from "react-use-error-boundary";
|
||||
|
||||
const Renderer = lazy(() => import("./RemarkRenderer"));
|
||||
|
||||
@ -6,7 +7,7 @@ export interface MarkdownProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export default function Markdown(props: MarkdownProps) {
|
||||
function Markdown(props: MarkdownProps) {
|
||||
if (!props.content) return null;
|
||||
|
||||
return (
|
||||
@ -15,3 +16,5 @@ export default function Markdown(props: MarkdownProps) {
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default withErrorBoundary(Markdown);
|
||||
|
@ -1,121 +1,28 @@
|
||||
// adapted from Revite
|
||||
// https://github.com/revoltchat/revite/blob/fe63c6633f32b54aa1989cb34627e72bb3377efd/src/components/markdown/RemarkRenderer.tsx
|
||||
|
||||
import React from "react";
|
||||
import * as prod from "react/jsx-runtime";
|
||||
import rehypePrism from "rehype-prism";
|
||||
import rehypeReact from "rehype-react";
|
||||
import { ReactMarkdown } from "react-markdown/lib/react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkParse from "remark-parse";
|
||||
import remarkRehype from "remark-rehype";
|
||||
import styled from "styled-components";
|
||||
import { unified } from "unified";
|
||||
import Link from "../Link";
|
||||
import { MarkdownProps } from "./Markdown";
|
||||
import RenderCodeblock from "./plugins/Codeblock";
|
||||
import "./prism";
|
||||
|
||||
/**
|
||||
* Null element
|
||||
*/
|
||||
const Null: React.FC = () => null;
|
||||
const Container = styled.div`
|
||||
// remove the excessive left padding in lists
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 0 0 15px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Markdown components
|
||||
*/
|
||||
const components = {
|
||||
// emoji: RenderEmoji,
|
||||
// mention: RenderMention,
|
||||
// spoiler: RenderSpoiler,
|
||||
// channel: RenderChannel,
|
||||
a: Link,
|
||||
p: styled.p`
|
||||
margin: 0;
|
||||
|
||||
> code {
|
||||
padding: 1px 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`,
|
||||
h1: styled.h1`
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
h2: styled.h2`
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
h3: styled.h3`
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
h4: styled.h4`
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
h5: styled.h5`
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
h6: styled.h6`
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
pre: RenderCodeblock,
|
||||
code: styled.code`
|
||||
color: var(--text);
|
||||
background: var(--background-secondary);
|
||||
|
||||
font-size: 90%;
|
||||
font-family: var(--font-family-code);
|
||||
|
||||
border-radius: 4px;
|
||||
box-decoration-break: clone;
|
||||
`,
|
||||
table: styled.table`
|
||||
border-collapse: collapse;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 6px;
|
||||
border: 1px solid var(--background-teritary);
|
||||
}
|
||||
`,
|
||||
ul: styled.ul`
|
||||
list-style-position: inside;
|
||||
padding: 0;
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
ol: styled.ol`
|
||||
list-style-position: inside;
|
||||
padding: 0;
|
||||
margin: 0.2em 0;
|
||||
`,
|
||||
|
||||
blockquote: styled.blockquote`
|
||||
blockquote {
|
||||
margin: 2px 0;
|
||||
padding: 2px 0;
|
||||
background: red;
|
||||
padding: 5px;
|
||||
background-color: var(--background-secondary);
|
||||
width: fit-content;
|
||||
border-radius: 4px;
|
||||
border-inline-start: 4px solid var(--background-teritary);
|
||||
|
||||
> * {
|
||||
margin: 0 8px;
|
||||
}
|
||||
`,
|
||||
// Block image elements
|
||||
img: Null,
|
||||
// Catch literally everything else just in case
|
||||
video: Null,
|
||||
figure: Null,
|
||||
picture: Null,
|
||||
source: Null,
|
||||
audio: Null,
|
||||
script: Null,
|
||||
style: Null,
|
||||
};
|
||||
|
||||
const render = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkRehype)
|
||||
.use(rehypePrism)
|
||||
// @ts-expect-error typescript doesn't like this
|
||||
.use(rehypeReact, { Fragment: prod.Fragment, jsx: prod.jsx, jsxs: prod.jsxs, components });
|
||||
border-inline-start: 4px solid var(--background-tertiary);
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Regex for matching execessive recursion of blockquotes and lists
|
||||
@ -176,11 +83,42 @@ function sanitize(content: string) {
|
||||
export default React.memo(({ content }: MarkdownProps) => {
|
||||
const sanitizedContent = React.useMemo(() => sanitize(content), [content]);
|
||||
|
||||
const [parsedContent, setParsedContent] = React.useState<React.ReactElement>(null!);
|
||||
|
||||
React.useEffect(() => {
|
||||
render.process(sanitizedContent).then((file) => setParsedContent(file.result));
|
||||
}, [sanitizedContent]);
|
||||
|
||||
return parsedContent;
|
||||
return (
|
||||
<Container>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[[remarkGfm, { singleTilde: false }]]}
|
||||
children={sanitizedContent}
|
||||
// @ts-expect-error idk what is going on here, but this is correct
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
children={String(children).replace(/\n$/, "")}
|
||||
language={match[1]}
|
||||
// @ts-expect-error oh fk off typescript, dark is ok for you but not dracula? ok
|
||||
style={dracula}
|
||||
PreTag="section" // parent tag
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
a({ node, href, children, ...props }) {
|
||||
return (
|
||||
<Link href={href} target="_blank" rel="noopener noreferrer" {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
blockquote({ node, children }) {
|
||||
return <blockquote>{children}</blockquote>;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
97
src/components/markdown/prism.ts
Normal file
97
src/components/markdown/prism.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import Prism from "prismjs";
|
||||
|
||||
import "prismjs/components/prism-actionscript";
|
||||
import "prismjs/components/prism-ada";
|
||||
import "prismjs/components/prism-apacheconf";
|
||||
import "prismjs/components/prism-applescript";
|
||||
// import "prismjs/components/prism-arduino"; // throws `land2 is undefined`
|
||||
import "prismjs/components/prism-asciidoc";
|
||||
import "prismjs/components/prism-asm6502";
|
||||
import "prismjs/components/prism-autohotkey";
|
||||
import "prismjs/components/prism-autoit";
|
||||
import "prismjs/components/prism-bash.min.js";
|
||||
import "prismjs/components/prism-basic";
|
||||
import "prismjs/components/prism-batch";
|
||||
import "prismjs/components/prism-brainfuck";
|
||||
import "prismjs/components/prism-c.min.js";
|
||||
import "prismjs/components/prism-clojure";
|
||||
import "prismjs/components/prism-coffeescript";
|
||||
import "prismjs/components/prism-coq";
|
||||
import "prismjs/components/prism-cpp.min.js";
|
||||
import "prismjs/components/prism-csharp";
|
||||
import "prismjs/components/prism-dart";
|
||||
import "prismjs/components/prism-diff";
|
||||
import "prismjs/components/prism-dns-zone-file";
|
||||
import "prismjs/components/prism-docker";
|
||||
import "prismjs/components/prism-ebnf";
|
||||
import "prismjs/components/prism-elixir";
|
||||
import "prismjs/components/prism-elm";
|
||||
import "prismjs/components/prism-erlang";
|
||||
import "prismjs/components/prism-excel-formula";
|
||||
import "prismjs/components/prism-fortran";
|
||||
import "prismjs/components/prism-fsharp";
|
||||
import "prismjs/components/prism-glsl";
|
||||
import "prismjs/components/prism-gml";
|
||||
import "prismjs/components/prism-go";
|
||||
import "prismjs/components/prism-graphql";
|
||||
import "prismjs/components/prism-haml";
|
||||
import "prismjs/components/prism-haskell";
|
||||
import "prismjs/components/prism-haxe";
|
||||
import "prismjs/components/prism-http";
|
||||
import "prismjs/components/prism-ini";
|
||||
import "prismjs/components/prism-java";
|
||||
import "prismjs/components/prism-javastacktrace";
|
||||
import "prismjs/components/prism-json.min.js";
|
||||
import "prismjs/components/prism-json5.min.js";
|
||||
import "prismjs/components/prism-jsstacktrace";
|
||||
import "prismjs/components/prism-kotlin";
|
||||
import "prismjs/components/prism-latex";
|
||||
import "prismjs/components/prism-less";
|
||||
import "prismjs/components/prism-lisp";
|
||||
import "prismjs/components/prism-livescript";
|
||||
import "prismjs/components/prism-llvm";
|
||||
import "prismjs/components/prism-lua";
|
||||
import "prismjs/components/prism-makefile";
|
||||
import "prismjs/components/prism-markdown";
|
||||
import "prismjs/components/prism-matlab";
|
||||
import "prismjs/components/prism-mel";
|
||||
import "prismjs/components/prism-mizar";
|
||||
import "prismjs/components/prism-mongodb";
|
||||
import "prismjs/components/prism-moonscript";
|
||||
import "prismjs/components/prism-nasm";
|
||||
import "prismjs/components/prism-nginx";
|
||||
import "prismjs/components/prism-nim";
|
||||
import "prismjs/components/prism-objectivec";
|
||||
import "prismjs/components/prism-ocaml";
|
||||
import "prismjs/components/prism-perl";
|
||||
import "prismjs/components/prism-powershell";
|
||||
import "prismjs/components/prism-processing";
|
||||
import "prismjs/components/prism-protobuf";
|
||||
import "prismjs/components/prism-python";
|
||||
import "prismjs/components/prism-qml";
|
||||
import "prismjs/components/prism-r";
|
||||
import "prismjs/components/prism-reason";
|
||||
import "prismjs/components/prism-ruby";
|
||||
import "prismjs/components/prism-rust";
|
||||
import "prismjs/components/prism-sass";
|
||||
import "prismjs/components/prism-scala";
|
||||
import "prismjs/components/prism-scheme";
|
||||
import "prismjs/components/prism-scss";
|
||||
import "prismjs/components/prism-shell-session";
|
||||
import "prismjs/components/prism-sml";
|
||||
import "prismjs/components/prism-sql";
|
||||
import "prismjs/components/prism-stan";
|
||||
import "prismjs/components/prism-swift";
|
||||
import "prismjs/components/prism-toml";
|
||||
import "prismjs/components/prism-typescript";
|
||||
import "prismjs/components/prism-vim";
|
||||
import "prismjs/components/prism-visual-basic";
|
||||
import "prismjs/components/prism-wasm";
|
||||
import "prismjs/components/prism-wolfram";
|
||||
import "prismjs/components/prism-xquery";
|
||||
import "prismjs/components/prism-yaml";
|
||||
|
||||
import "prismjs/themes/prism-tomorrow.css";
|
||||
// import "./prism.css"; // TODO: customizable theme and default theme selection
|
||||
|
||||
export default Prism;
|
@ -7,6 +7,12 @@ import { MessageLike } from "../../stores/objects/Message";
|
||||
const Container = styled.div`
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-medium);
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
|
@ -70,6 +70,7 @@ export const MessageContent = styled.div`
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-right: 48px;
|
||||
word-wrap: anywhere;
|
||||
`;
|
||||
|
||||
export const DetailBase = styled.div`
|
||||
|
Loading…
Reference in New Issue
Block a user