1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
import React from "react";
import CopyBtn from "@/components/ui/copy-button";
import { cn } from "@/lib/utils";
import Markdown from "react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { dracula } from "react-syntax-highlighter/dist/cjs/styles/prism";
import remarkBreaks from "remark-breaks";
import remarkGfm from "remark-gfm";
function PreWithCopyBtn({ className, ...props }: React.ComponentProps<"pre">) {
const ref = React.useRef<HTMLPreElement>(null);
return (
<span className="group relative">
<CopyBtn
className="absolute right-1 top-1 m-1 hidden text-white group-hover:block"
getStringToCopy={() => {
return ref.current?.textContent ?? "";
}}
/>
<pre ref={ref} className={cn(className, "")} {...props} />
</span>
);
}
export function MarkdownReadonly({
children: markdown,
className,
onSave,
}: {
children: string;
className?: string;
onSave?: (markdown: string) => void;
}) {
/**
* This method is triggered when a checkbox is toggled from the masonry view
* It finds the index of the clicked checkbox inside of the note
* It then finds the corresponding markdown and changes it accordingly
*/
const handleTodoClick = (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
const parent = e.target.closest(".prose");
if (!parent) return;
const allCheckboxes = parent.querySelectorAll(".todo-checkbox");
let checkboxIndex = 0;
allCheckboxes.forEach((cb, i) => {
if (cb === e.target) checkboxIndex = i;
});
let i = 0;
const todoPattern = /^(\s*[-*+]\s*\[)( |x|X)(\])/gm;
const newMarkdown = markdown.replace(
todoPattern,
(match, prefix: string, state: string, suffix: string) => {
const currentIndex = i++;
if (currentIndex !== checkboxIndex) {
return match;
}
const isDone = state.toLowerCase() === "x";
const nextState = isDone ? " " : "x";
return `${prefix}${nextState}${suffix}`;
},
);
if (onSave) {
onSave(newMarkdown);
}
};
return (
<Markdown
remarkPlugins={[remarkGfm, remarkBreaks]}
className={cn("prose dark:prose-invert", className)}
components={{
input: (props) =>
props.type === "checkbox" ? (
<input
checked={props.checked}
onChange={handleTodoClick}
type="checkbox"
className="todo-checkbox"
/>
) : (
<input {...props} readOnly />
),
pre({ ...props }) {
return <PreWithCopyBtn {...props} />;
},
code({ className, children, ...props }) {
const match = /language-(\w+)/.exec(className ?? "");
return match ? (
<SyntaxHighlighter
PreTag="div"
language={match[1]}
{...props}
style={dracula}
>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{markdown}
</Markdown>
);
}
|