feat: copy code, code highlight

This commit is contained in:
Artemy 2023-04-02 09:54:03 +03:00
parent 866181e48a
commit 20a09255a3
9 changed files with 534 additions and 16 deletions

View file

@ -2,7 +2,7 @@ function CheckBox(props) {
return (
<div className={`form-check m-5 ${props.className}`}>
<input
className="form-check-input appearance-none h-3 w-6 duration-500 checked:bg-blue-600 checked:border-blue-600 pr-3 border border-gray-300 rounded-lg mr-1 checked:shadow-lg checked:shadow-blue-600 transition ease-out"
className="form-check-input appearance-none h-3 w-6 duration-500 checked:bg-blue-600 checked:border-blue-600 pr-3 border border-gray-300 rounded-lg mr-1 checked:shadow-lg checked:shadow-blue-600 transition ease-out cursor-pointer"
type="checkbox"
id={props.id}
defaultChecked={props.checked}
@ -11,7 +11,7 @@ function CheckBox(props) {
/>
<label
title={props.title}
className="form-check-label inline-block"
className="form-check-label inline-block cursor-pointer"
htmlFor={props.id}
>
{props.label}

View file

@ -6,7 +6,6 @@ function CopyToClipboard(props) {
let [copied, setCopied] = useState(false);
useEffect(() => {
console.log(copied);
if (copied === true) {
setTimeout(() => {
setCopied(false);
@ -23,6 +22,36 @@ function CopyToClipboard(props) {
}}
>
<div className="col-span-3 truncate">{props.text}</div>
<div className="justify-self-center lg:justify-self-end cursor-pointer">
{copied === true ? (
<CheckIcon className="transform translate-z-0 h-7 w-7" />
) : (
<ClipboardIcon className="transform translate-z-0 h-7 w-7" />
)}
</div>
</div>
);
}
function CodeCopyBtn(props) {
let [copied, setCopied] = useState(false);
useEffect(() => {
if (copied === true) {
setTimeout(() => {
setCopied(false);
}, 1000);
}
});
return (
<div
className="code-copy-btn"
onClick={() => {
navigator.clipboard.writeText(props.children[0].props.children[0]);
setCopied(true);
}}
>
<div className="justify-self-center lg:justify-self-end">
{copied === true ? (
<CheckIcon className="transform translate-z-0 h-7 w-7" />
@ -34,4 +63,4 @@ function CopyToClipboard(props) {
);
}
export { CopyToClipboard };
export { CopyToClipboard, CodeCopyBtn };

View file

@ -2,14 +2,47 @@ import rehypeMathjax from "rehype-mathjax";
import remarkMath from "remark-math";
import ReactMarkdown from "react-markdown";
import SyntaxHighlighter from "react-syntax-highlighter";
import { darcula, github } from "react-syntax-highlighter/dist/esm/styles/hljs";
import { CodeCopyBtn } from "./copytocb";
let theme = window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
function CodeBlock(props) {
return (
<SyntaxHighlighter
className={`md-code ${props.className}`}
style={theme == "light" ? github : darcula}
>
{props.children[0]}
</SyntaxHighlighter>
);
}
function RenderMarkdown(props) {
return (
<ReactMarkdown
children={props.children}
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeMathjax]}
components={{
code: CodeBlock,
pre: Pre,
}}
/>
);
}
function Pre({ children }) {
return (
<pre className="blog-pre">
<CodeCopyBtn>{children}</CodeCopyBtn>
{children}
</pre>
);
}
export default RenderMarkdown;

View file

@ -3,8 +3,9 @@
@tailwind utilities;
/* styles for displaying markdown */
@layer base {
/* styles for displaying markdown */
.md h1 {
@apply text-6xl;
}
@ -28,6 +29,7 @@
@apply border-l-4 pl-2 rounded-lg ml-4 mt-2 mb-2;
@apply bg-zinc-200 border-zinc-400;
@apply dark:bg-zinc-800 dark:border-zinc-600;
@apply pb-2;
}
.md hr {
@ -50,7 +52,7 @@
@apply list-decimal list-inside ml-4;
}
.md code {
.md-code {
@apply p-1;
@apply rounded-lg;
@apply bg-zinc-200 border-zinc-400;
@ -66,4 +68,21 @@
}
.md p, .math-inline { display: inline-block; }
.md li p {
display: inline;
}
/* other styles */
.blog-pre {
@apply mt-2 mb-2 relative;
}
.code-copy-btn {
@apply text-zinc-400 dark:text-zinc-300 absolute right-2 top-2 cursor-pointer;
}
.code-copy-btn:hover {
@apply text-zinc-500 dark:text-zinc-400;
}
}

View file

@ -3,6 +3,7 @@ import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
import { CheckBox } from "../components/checkbox";
import { useState } from "react";
import RenderMarkdown from "../components/markdown";
import printDate from "../components/utils";
function CreateNote() {
const [preview, setPreview] = useState(false);
@ -10,12 +11,20 @@ function CreateNote() {
const [text, setText] = useState(localStorage.getItem("NoteText"));
const [name, setName] = useState(localStorage.getItem("NoteName"));
const [date, setDate] = useState(Date.now());
setInterval(() => {
if (preview) {
setDate(Date.now());
}
}, 1000);
let inputStyle = `form-control block px-3 py-1.5 text-base font-normal text-gray-700 dark:text-white bg-white dark:bg-zinc-900 bg-clip-padding border border-solid border-gray-300 rounded-lg transition ease-in-out focus:border-blue-600 focus:outline-none`;
return (
<div>
<div className="grid grid-cols-1 lg:grid-cols-2">
<h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2">
Написать заметку
{`${preview ? "" : "Написать заметку"}`}
</h2>
<CheckBox
className="justify-self-center lg:justify-self-end"
@ -27,13 +36,15 @@ function CreateNote() {
<input
type="text"
className={`mb-2 md:w-1/6 w-full ${inputStyle}`}
className={`mb-2 md:w-1/6 w-full ${inputStyle} ${
preview ? "hidden" : ""
}`}
placeholder="Название заметки..."
maxLength={64}
value={localStorage.getItem("NoteName") || ""}
onChange={(e) => {
localStorage.setItem("NoteName", e.target.value);
setName(e.target.value);
localStorage.setItem("NoteName", e.target.value);
}}
/>
<textarea
@ -46,12 +57,22 @@ function CreateNote() {
placeholder="Ваша заметка начинается здесь. Можно использовать markdown..."
maxLength={5000}
onChange={(e) => {
localStorage.setItem("NoteText", e.target.value);
setText(e.target.value);
localStorage.setItem("NoteText", e.target.value);
}}
value={localStorage.getItem("NoteText") || ""}
></textarea>
{preview && (
<div className="grid grid-cols-1 lg:grid-cols-2">
<h2 className="font-medium text-center lg:text-left leading-tight text-4xl mt-0 mb-2">
{name}
</h2>
<div className="justify-self-center lg:justify-self-end">
{printDate(Date.now())}
</div>
</div>
)}
{preview && (
<div className="w-full md break-words">
<RenderMarkdown>{text}</RenderMarkdown>
@ -80,7 +101,7 @@ function CreateNote() {
<ChevronDoubleRightIcon className="transform translate-z-0 h-7 w-7" />
}
>
Отправить
{publicState ? "Сохранить" : "Опубликовать"}
</IconWithText>
</Button>
</div>

View file

@ -1,8 +1,12 @@
import printDate from "../components/utils";
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/outline";
import { Button, IconWithText } from "../components/button";
import { useSearchParams } from "react-router-dom";
function PubError() {
const [searchParams] = useSearchParams();
let err = searchParams.get("err");
return (
<div className="">
<Button className="mb-4" href="/">
@ -24,7 +28,7 @@ function PubError() {
</div>
</div>
<div className="w-full md">
Заметка не была опубликована из-за неизвестной ошибки
{err ? err : "Заметка не была опубликована из-за неизвестной ошибки"}
</div>
</div>
</div>

View file

@ -9,11 +9,19 @@ function Publish() {
if (!done) {
done = true;
let err = false;
const note = {
name: localStorage.getItem("NoteName"),
text: localStorage.getItem("NoteText"),
};
if (!note.name) {
err = "Заметка не была опубликована, так как отсутствует название.";
}
if (!note.text) {
err = "Заметка не была опубликована, так как отсутствует текст.";
}
fetch(`/publish`, {
method: "POST",
headers: {
@ -30,7 +38,7 @@ function Publish() {
navigate(`/pubNotesSafe/${data.id}`, { replace: true });
})
.catch(() => {
navigate(`/pubError`, { replace: true });
navigate(`/pubError?err=${err}`, { replace: true });
});
})
.catch(() => {