aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web
diff options
context:
space:
mode:
authorkamtschatka <sschatka@gmail.com>2024-06-09 15:30:56 +0200
committerGitHub <noreply@github.com>2024-06-09 14:30:56 +0100
commitbe1bb388924f4422058099dcb0debdd1c857d36a (patch)
treebd518e1df0a137f88950b4f7f083da90e8e29cbb /apps/web
parentf7a77533240ec435c8a7b103b6f6be409bf995d8 (diff)
downloadkarakeep-be1bb388924f4422058099dcb0debdd1c857d36a.tar.zst
feature(web): Add syntax highlighting to code blocks and a quick copy button. Fixes #195 (#197)
* Any plans to support copy to clipboard (markdown code) for notes? #195 added a button to copy the markdown and added code highlighting * Any plans to support copy to clipboard (markdown code) for notes? #195 Changed the copy-button to a generic one added a safeguard and a message to the copy button if copying is not possible * Some code cleanups --------- Co-authored-by: kamtschatka <simon.schatka@gmx.at> Co-authored-by: MohamedBassem <me@mbassem.com>
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/components/dashboard/bookmarks/TextCard.tsx8
-rw-r--r--apps/web/components/dashboard/preview/TextContentSection.tsx6
-rw-r--r--apps/web/components/dashboard/settings/AddApiKey.tsx22
-rw-r--r--apps/web/components/ui/copy-button.tsx37
-rw-r--r--apps/web/components/ui/markdown-component.tsx58
-rw-r--r--apps/web/package.json2
6 files changed, 107 insertions, 26 deletions
diff --git a/apps/web/components/dashboard/bookmarks/TextCard.tsx b/apps/web/components/dashboard/bookmarks/TextCard.tsx
index 74b3e8e5..b18efc3d 100644
--- a/apps/web/components/dashboard/bookmarks/TextCard.tsx
+++ b/apps/web/components/dashboard/bookmarks/TextCard.tsx
@@ -1,10 +1,10 @@
"use client";
import { useState } from "react";
+import { MarkdownComponent } from "@/components/ui/markdown-component";
import { api } from "@/lib/trpc";
import { bookmarkLayoutSwitch } from "@/lib/userLocalSettings/bookmarksLayout";
import { cn } from "@/lib/utils";
-import Markdown from "react-markdown";
import type { ZBookmark } from "@hoarder/shared/types/bookmarks";
import { isBookmarkStillTagging } from "@hoarder/shared-react/utils/bookmarkUtils";
@@ -52,11 +52,7 @@ export default function TextCard({
/>
<BookmarkLayoutAdaptingCard
title={bookmark.title}
- content={
- <Markdown className="prose dark:prose-invert">
- {bookmarkedText.text}
- </Markdown>
- }
+ content={<MarkdownComponent>{bookmarkedText.text}</MarkdownComponent>}
footer={null}
wrapTags={true}
bookmark={bookmark}
diff --git a/apps/web/components/dashboard/preview/TextContentSection.tsx b/apps/web/components/dashboard/preview/TextContentSection.tsx
index eba7d28b..2df1e964 100644
--- a/apps/web/components/dashboard/preview/TextContentSection.tsx
+++ b/apps/web/components/dashboard/preview/TextContentSection.tsx
@@ -1,5 +1,5 @@
+import { MarkdownComponent } from "@/components/ui/markdown-component";
import { ScrollArea } from "@radix-ui/react-scroll-area";
-import Markdown from "react-markdown";
import type { ZBookmark } from "@hoarder/shared/types/bookmarks";
@@ -9,9 +9,7 @@ export function TextContentSection({ bookmark }: { bookmark: ZBookmark }) {
}
return (
<ScrollArea className="h-full">
- <Markdown className="prose mx-auto dark:prose-invert">
- {bookmark.content.text}
- </Markdown>
+ <MarkdownComponent>{bookmark.content.text}</MarkdownComponent>
</ScrollArea>
);
}
diff --git a/apps/web/components/dashboard/settings/AddApiKey.tsx b/apps/web/components/dashboard/settings/AddApiKey.tsx
index 15a78d56..34fd2df7 100644
--- a/apps/web/components/dashboard/settings/AddApiKey.tsx
+++ b/apps/web/components/dashboard/settings/AddApiKey.tsx
@@ -5,6 +5,7 @@ import { useState } from "react";
import { useRouter } from "next/navigation";
import { ActionButton } from "@/components/ui/action-button";
import { Button } from "@/components/ui/button";
+import CopyBtn from "@/components/ui/copy-button";
import {
Dialog,
DialogClose,
@@ -28,19 +29,10 @@ import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/use-toast";
import { api } from "@/lib/trpc";
import { zodResolver } from "@hookform/resolvers/zod";
-import { Check, Copy } from "lucide-react";
import { useForm } from "react-hook-form";
import { z } from "zod";
function ApiKeySuccess({ apiKey }: { apiKey: string }) {
- const [isCopied, setCopied] = useState(false);
-
- const onCopy = () => {
- navigator.clipboard.writeText(apiKey);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- };
-
return (
<div>
<div className="py-4">
@@ -49,13 +41,11 @@ function ApiKeySuccess({ apiKey }: { apiKey: string }) {
</div>
<div className="flex space-x-2 pt-2">
<Input value={apiKey} readOnly />
- <Button onClick={onCopy}>
- {!isCopied ? (
- <Copy className="size-4" />
- ) : (
- <Check className="size-4" />
- )}
- </Button>
+ <CopyBtn
+ getStringToCopy={() => {
+ return apiKey;
+ }}
+ />
</div>
</div>
);
diff --git a/apps/web/components/ui/copy-button.tsx b/apps/web/components/ui/copy-button.tsx
new file mode 100644
index 00000000..a51ce902
--- /dev/null
+++ b/apps/web/components/ui/copy-button.tsx
@@ -0,0 +1,37 @@
+import React, { useEffect } from "react";
+import { Check, Copy } from "lucide-react";
+
+export default function CopyBtn({
+ className,
+ getStringToCopy,
+}: {
+ className?: string;
+ getStringToCopy: () => string;
+}) {
+ const [copyOk, setCopyOk] = React.useState(false);
+ const [disabled, setDisabled] = React.useState(false);
+ useEffect(() => {
+ if (!navigator || !navigator.clipboard) {
+ setDisabled(true);
+ }
+ });
+
+ const handleClick = async () => {
+ await navigator.clipboard.writeText(getStringToCopy());
+ setCopyOk(true);
+ setTimeout(() => {
+ setCopyOk(false);
+ }, 2000);
+ };
+
+ return (
+ <button
+ className={className}
+ onClick={handleClick}
+ disabled={disabled}
+ title={disabled ? "Copying is only available over https" : undefined}
+ >
+ {copyOk ? <Check /> : <Copy />}
+ </button>
+ );
+}
diff --git a/apps/web/components/ui/markdown-component.tsx b/apps/web/components/ui/markdown-component.tsx
new file mode 100644
index 00000000..f92cb3a3
--- /dev/null
+++ b/apps/web/components/ui/markdown-component.tsx
@@ -0,0 +1,58 @@
+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";
+
+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 MarkdownComponent({
+ children: markdown,
+}: {
+ children: string;
+}) {
+ return (
+ <Markdown
+ className="prose dark:prose-invert"
+ components={{
+ pre({ ...props }) {
+ return <PreWithCopyBtn {...props} />;
+ },
+ code({ className, children, ...props }) {
+ const match = /language-(\w+)/.exec(className ?? "");
+ return match ? (
+ // @ts-expect-error -- Refs are not compatible for some reason
+ <SyntaxHighlighter
+ PreTag="div"
+ language={match[1]}
+ {...props}
+ style={dracula}
+ >
+ {String(children).replace(/\n$/, "")}
+ </SyntaxHighlighter>
+ ) : (
+ <code className={className} {...props}>
+ {children}
+ </code>
+ );
+ },
+ }}
+ >
+ {markdown}
+ </Markdown>
+ );
+}
diff --git a/apps/web/package.json b/apps/web/package.json
index ebec0278..91743602 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -62,6 +62,7 @@
"react-markdown": "^9.0.1",
"react-masonry-css": "^1.0.16",
"react-select": "^5.8.0",
+ "react-syntax-highlighter": "^15.5.0",
"sharp": "^0.33.3",
"superjson": "^2.2.1",
"tailwind-merge": "^2.2.1",
@@ -76,6 +77,7 @@
"@types/emoji-mart": "^3.0.14",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
+ "@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.17",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",