mirror of
https://github.com/artegoser/AnoPaper.git
synced 2025-02-23 12:43:14 +03:00
feat: copy code, code highlight
This commit is contained in:
parent
866181e48a
commit
20a09255a3
9 changed files with 534 additions and 16 deletions
|
@ -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}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
Loading…
Add table
Reference in a new issue