Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"@kleros/ui-components-library": "^2.20.0",
"@lifi/wallet-management": "^3.7.1",
"@lifi/widget": "^3.18.1",
"@mdxeditor/editor": "^3.45.0",
"@reown/appkit": "^1.7.1",
"@reown/appkit-adapter-wagmi": "^1.7.1",
"@sentry/react": "^7.120.0",
Expand Down
22 changes: 7 additions & 15 deletions web/src/components/EvidenceCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useMemo } from "react";
import styled, { css } from "styled-components";

import ReactMarkdown from "react-markdown";
import { useParams } from "react-router-dom";

import { Card } from "@kleros/ui-components-library";
Expand All @@ -18,9 +17,11 @@ import { hoverShortTransitionTiming } from "styles/commonStyles";
import { landscapeStyle } from "styles/landscapeStyle";
import { responsiveSize } from "styles/responsiveSize";

import JurorTitle from "pages/Home/TopJurors/JurorCard/JurorTitle";

import { ExternalLink } from "./ExternalLink";
import { InternalLink } from "./InternalLink";
import JurorTitle from "pages/Home/TopJurors/JurorCard/JurorTitle";
import MarkdownRenderer from "./MarkdownRenderer";

const StyledCard = styled(Card)`
width: 100%;
Expand Down Expand Up @@ -66,17 +67,6 @@ const Index = styled.p`
`;

const ReactMarkdownWrapper = styled.div``;
const StyledReactMarkdown = styled(ReactMarkdown)`
a {
font-size: 16px;
}
code {
color: ${({ theme }) => theme.secondaryText};
}
p {
margin: 0;
}
`;

const BottomShade = styled.div`
background-color: ${({ theme }) => theme.lightBlue};
Expand Down Expand Up @@ -227,10 +217,12 @@ const EvidenceCard: React.FC<IEvidenceCard> = ({
</IndexAndName>
{name && description ? (
<ReactMarkdownWrapper dir="auto">
<StyledReactMarkdown>{description}</StyledReactMarkdown>
<MarkdownRenderer content={description} />
</ReactMarkdownWrapper>
) : (
<p>{evidence}</p>
<ReactMarkdownWrapper dir="auto">
<MarkdownRenderer content={evidence} />
</ReactMarkdownWrapper>
)}
</TopContent>
<BottomShade>
Expand Down
155 changes: 155 additions & 0 deletions web/src/components/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import React, { useRef } from "react";
import styled from "styled-components";

import {
MDXEditor,
type MDXEditorMethods,
type MDXEditorProps,
headingsPlugin,
listsPlugin,
quotePlugin,
thematicBreakPlugin,
markdownShortcutPlugin,
linkPlugin,
linkDialogPlugin,
tablePlugin,
toolbarPlugin,
UndoRedo,
BoldItalicUnderlineToggles,
CodeToggle,
ListsToggle,
CreateLink,
InsertTable,
BlockTypeSelect,
Separator,
} from "@mdxeditor/editor";

import InfoIcon from "svgs/icons/info-circle.svg";

import { sanitizeMarkdown } from "utils/markdownSanitization";

import { MDXEditorContainer, MDXEditorGlobalStyles } from "styles/mdxEditorTheme";

import "@mdxeditor/editor/style.css";

const Container = styled(MDXEditorContainer)<{ isEmpty: boolean }>``;

const MessageContainer = styled.div`
display: flex;
align-items: flex-start;
gap: 8px;
margin-top: 8px;
`;

const MessageText = styled.small`
font-size: 14px;
font-weight: 400;
color: ${({ theme }) => theme.secondaryText};
hyphens: auto;
line-height: 1.4;
`;

const StyledInfoIcon = styled(InfoIcon)`
width: 16px;
height: 16px;
fill: ${({ theme }) => theme.secondaryText} !important;
flex-shrink: 0;
margin-top: 2px;
path {
fill: ${({ theme }) => theme.secondaryText} !important;
}
* {
fill: ${({ theme }) => theme.secondaryText} !important;
}
`;

interface IMarkdownEditor {
value: string;
onChange: (value: string) => void;
placeholder?: string;
showMessage?: boolean;
}

const MarkdownEditor: React.FC<IMarkdownEditor> = ({
value,
onChange,
placeholder = "Justify your vote...",
showMessage = true,
}) => {
const editorRef = useRef<MDXEditorMethods>(null);

const handleChange = (markdown: string) => {
const cleanedMarkdown = markdown === "\u200B" ? "" : markdown.replace(/^\u200B/, "");
const sanitizedMarkdown = sanitizeMarkdown(cleanedMarkdown);
onChange(sanitizedMarkdown);
};

const handleContainerClick = () => {
if (isEmpty && editorRef.current) {
editorRef.current.setMarkdown("\u200B");
setTimeout(() => {
if (editorRef.current) {
editorRef.current.focus();
}
}, 0);
}
};

const isEmpty = !value || value.trim() === "";

const editorProps: MDXEditorProps = {
markdown: value,
onChange: handleChange,
placeholder,
plugins: [
headingsPlugin(),
listsPlugin(),
quotePlugin(),
thematicBreakPlugin(),
markdownShortcutPlugin(),
linkPlugin(),
linkDialogPlugin(),
tablePlugin(),
toolbarPlugin({
toolbarContents: () => (
<>
<UndoRedo />
<Separator />
<BoldItalicUnderlineToggles />
<CodeToggle />
<Separator />
<BlockTypeSelect />
<Separator />
<ListsToggle />
<Separator />
<CreateLink />
<InsertTable />
</>
),
}),
],
};

return (
<>
<MDXEditorGlobalStyles />
<Container isEmpty={isEmpty} onClick={handleContainerClick} role="region" aria-label="Markdown editor">
<MDXEditor ref={editorRef} {...editorProps} aria-label="Rich text editor for markdown content" />
{showMessage && (
<MessageContainer>
<StyledInfoIcon />
<MessageText>
Please provide a comprehensive justification for your decision. Quality explanations are essential for the
parties involved and may be eligible for additional compensation in accordance with our justification
policy.
</MessageText>
</MessageContainer>
)}
</Container>
</>
);
};

export default MarkdownEditor;
54 changes: 54 additions & 0 deletions web/src/components/MarkdownRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react";

import {
MDXEditor,
type MDXEditorProps,
headingsPlugin,
listsPlugin,
quotePlugin,
thematicBreakPlugin,
markdownShortcutPlugin,
linkPlugin,
tablePlugin,
} from "@mdxeditor/editor";

import { sanitizeMarkdown } from "utils/markdownSanitization";

import { MDXRendererContainer } from "styles/mdxEditorTheme";

import "@mdxeditor/editor/style.css";

interface IMarkdownRenderer {
content: string;
className?: string;
}

const MarkdownRenderer: React.FC<IMarkdownRenderer> = ({ content, className }) => {
if (!content || content.trim() === "") {
return null;
}

const sanitizedContent = sanitizeMarkdown(content);

const editorProps: MDXEditorProps = {
markdown: sanitizedContent,
readOnly: true,
plugins: [
headingsPlugin(),
listsPlugin(),
quotePlugin(),
thematicBreakPlugin(),
markdownShortcutPlugin(),
linkPlugin(),
tablePlugin(),
],
};

return (
<MDXRendererContainer className={className} role="region" aria-label="Markdown content">
<MDXEditor {...editorProps} aria-label="Rendered markdown content" />
</MDXRendererContainer>
);
};

export default MarkdownRenderer;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Modal from "react-modal";
import { useWalletClient, usePublicClient, useConfig } from "wagmi";

import { Roles, useAtlasProvider } from "@kleros/kleros-app";
import { Textarea, Button, FileUploader } from "@kleros/ui-components-library";
import { Button, FileUploader } from "@kleros/ui-components-library";

import { simulateEvidenceModuleSubmitEvidence } from "hooks/contracts/generated";
import { wrapWithToast, errorToast, infoToast, successToast } from "utils/wrapWithToast";
Expand All @@ -14,6 +14,7 @@ import { getFileUploaderMsg, isEmpty } from "src/utils";

import EnsureAuth from "components/EnsureAuth";
import { EnsureChain } from "components/EnsureChain";
import MarkdownEditor from "components/MarkdownEditor";

const StyledModal = styled(Modal)`
position: absolute;
Expand All @@ -36,9 +37,12 @@ const StyledModal = styled(Modal)`
gap: 16px;
`;

const StyledTextArea = styled(Textarea)`
const EditorContainer = styled.div`
width: 100%;
height: 200px;

[class*="contentEditable"] {
min-height: 200px;
}
`;

const StyledFileUploader = styled(FileUploader)`
Expand Down Expand Up @@ -93,12 +97,14 @@ const SubmitEvidenceModal: React.FC<{
return (
<StyledModal {...{ isOpen }} shouldCloseOnEsc shouldCloseOnOverlayClick onRequestClose={close}>
<h1>Submit New Evidence</h1>
<StyledTextArea
dir="auto"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Your Arguments"
/>
<EditorContainer>
<MarkdownEditor
value={message}
onChange={setMessage}
placeholder="Describe your evidence in detail..."
showMessage={false}
/>
</EditorContainer>
<StyledFileUploader
callback={(file: File) => setFile(file)}
msg={getFileUploaderMsg(Roles.Evidence, roleRestrictions)}
Expand Down
5 changes: 2 additions & 3 deletions web/src/pages/Cases/CaseDetails/Voting/Classic/Reveal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";

import { EnsureChain } from "components/EnsureChain";
import InfoCard from "components/InfoCard";

import JustificationArea from "../JustificationArea";
import MarkdownEditor from "components/MarkdownEditor";

const Container = styled.div`
width: 100%;
Expand Down Expand Up @@ -114,7 +113,7 @@ const Reveal: React.FC<IReveal> = ({ arbitrable, voteIDs, setIsOpen, commit, isR
<ReactMarkdownWrapper dir="auto">
<ReactMarkdown>{disputeDetails?.question ?? ""}</ReactMarkdown>
</ReactMarkdownWrapper>
<JustificationArea {...{ justification, setJustification }} />
<MarkdownEditor value={justification} onChange={setJustification} />
<StyledEnsureChain>
<StyledButton
variant="secondary"
Expand Down
37 changes: 0 additions & 37 deletions web/src/pages/Cases/CaseDetails/Voting/JustificationArea.tsx

This file was deleted.

Loading
Loading