Skip to content

Commit 47f2d95

Browse files
committed
chore: adjust texteditor for precode
1 parent fa78162 commit 47f2d95

File tree

11 files changed

+119
-83
lines changed

11 files changed

+119
-83
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@radix-ui/react-tooltip": "^1.2.7",
3636
"@tanstack/react-query": "^5.85.5",
3737
"@tanstack/react-table": "^8.21.3",
38+
"@tiptap/extension-code-block-lowlight": "^3.4.1",
3839
"@tiptap/extension-horizontal-rule": "^3.4.1",
3940
"@tiptap/extension-image": "^3.2.0",
4041
"@tiptap/extension-link": "^3.2.0",
@@ -53,8 +54,10 @@
5354
"dompurify": "^3.2.6",
5455
"embla-carousel-autoplay": "^8.2.0",
5556
"embla-carousel-react": "^8.2.0",
57+
"highlight.js": "^11.11.1",
5658
"js-cookie": "^3.0.5",
5759
"jwt-decode": "^4.0.0",
60+
"lowlight": "^3.3.0",
5861
"lucide-react": "^0.536.0",
5962
"marked": "^16.2.1",
6063
"motion": "^12.7.4",

pnpm-lock.yaml

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/common/TextEditor/TextEditor.tsx

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,34 @@ import Typography from "@tiptap/extension-typography";
88
import Link from "@tiptap/extension-link";
99
import Placeholder from "@tiptap/extension-placeholder";
1010
import HorizontalRule from "@tiptap/extension-horizontal-rule";
11+
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
12+
import { createLowlight } from "lowlight";
13+
import js from "highlight.js/lib/languages/javascript";
14+
import ts from "highlight.js/lib/languages/typescript";
15+
import css from "highlight.js/lib/languages/css";
16+
import html from "highlight.js/lib/languages/xml";
17+
import python from "highlight.js/lib/languages/python";
1118
import { useState, useCallback, useMemo, useEffect } from "react";
1219
import { markdownToHtml, htmlToMarkdown } from "./utils";
20+
import "highlight.js/styles/github-dark.css";
1321

1422
import { Button } from "@/components/ui/Button";
1523
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card";
1624
import { Toolbar } from "@/components/common/TextEditor";
1725
import Loader from "../Loader";
1826

27+
const lowlight = createLowlight();
28+
29+
lowlight.register("js", js);
30+
lowlight.register("javascript", js);
31+
lowlight.register("ts", ts);
32+
lowlight.register("typescript", ts);
33+
lowlight.register("css", css);
34+
lowlight.register("html", html);
35+
lowlight.register("xml", html);
36+
lowlight.register("python", python);
37+
lowlight.register("py", python);
38+
1939
interface TextEditorProps {
2040
markdownOutput?: boolean;
2141
value?: string;
@@ -29,7 +49,12 @@ const TextEditor = ({ markdownOutput = false, value, onChange }: TextEditorProps
2949

3050
const editor = useEditor({
3151
extensions: [
32-
StarterKit,
52+
StarterKit.configure({
53+
codeBlock: false,
54+
}),
55+
CodeBlockLowlight.configure({
56+
lowlight,
57+
}),
3358
Underline,
3459
Typography,
3560
HorizontalRule,
@@ -57,14 +82,12 @@ const TextEditor = ({ markdownOutput = false, value, onChange }: TextEditorProps
5782
const html = editor.getHTML();
5883
const markdown = htmlToMarkdown(html);
5984
setMarkdownContent(markdown);
60-
onChange?.(html);
61-
console.log("markdown: ", markdown);
62-
console.log("html: ", html);
85+
onChange?.(htmlToMarkdown(html));
6386
},
6487
editorProps: {
6588
attributes: {
6689
class:
67-
"prose prose-md dark:prose-invert max-w-none mx-auto focus:outline-none max-h-[300px] p-3 prose-blockquote:border-primary prose-blockquote:bg-muted/50 prose-blockquote:pl-4 prose-blockquote:py-1 prose-blockquote:before:content-none prose-blockquote:not-italic prose-code:bg-muted prose-code:rounded prose-code:before:content-none prose-code:after:content-none prose-pre:bg-muted prose-pre:border prose-pre:text-foreground prose-pre:p-3 prose-p:my-1 prose-h1:my-2 prose-h2:my-2 prose-h3:my-1 prose-ul:my-1 prose-ol:my-1 prose-li:my-0 prose-hr:my-3",
90+
"prose prose-md dark:prose-invert max-w-none mx-auto focus:outline-none max-h-[300px] p-3 prose-blockquote:border-primary prose-blockquote:bg-muted/50 prose-blockquote:pl-4 prose-blockquote:py-1 prose-blockquote:before:content-none prose-blockquote:not-italic prose-code:rounded prose-code:before:content-none prose-code:after:content-none prose-pre:border prose-pre:p-3 prose-p:my-1 prose-h1:my-2 prose-h2:my-2 prose-h3:my-1 prose-ul:my-1 prose-ol:my-1 prose-li:my-0 prose-hr:my-3",
6891
},
6992
},
7093
});
@@ -98,18 +121,6 @@ const TextEditor = ({ markdownOutput = false, value, onChange }: TextEditorProps
98121
input.click();
99122
}, [editor]);
100123

101-
const downloadMarkdown = useCallback(() => {
102-
const blob = new Blob([markdownContent], { type: "text/markdown" });
103-
const url = URL.createObjectURL(blob);
104-
const a = document.createElement("a");
105-
a.href = url;
106-
a.download = "document.md";
107-
document.body.appendChild(a);
108-
a.click();
109-
document.body.removeChild(a);
110-
URL.revokeObjectURL(url);
111-
}, [markdownContent]);
112-
113124
const addLink = useCallback(() => {
114125
if (!editor) return;
115126

@@ -129,8 +140,13 @@ const TextEditor = ({ markdownOutput = false, value, onChange }: TextEditorProps
129140
}, [editor]);
130141

131142
useEffect(() => {
132-
if (editor && htmlContent !== undefined && editor.getHTML() !== htmlContent) {
133-
editor.commands.setContent(htmlContent);
143+
if (editor && htmlContent !== undefined) {
144+
const currentMarkdown = htmlToMarkdown(editor.getHTML());
145+
const incomingMarkdown = htmlToMarkdown(htmlContent);
146+
147+
if (currentMarkdown !== incomingMarkdown && !editor.isFocused) {
148+
editor.commands.setContent(htmlContent);
149+
}
134150
}
135151
}, [editor, htmlContent]);
136152

@@ -147,7 +163,7 @@ const TextEditor = ({ markdownOutput = false, value, onChange }: TextEditorProps
147163
onAddImage={addImage}
148164
onAddImageFromFile={addImageFromFile}
149165
onAddLink={addLink}
150-
onDownloadMarkdown={downloadMarkdown}
166+
// onDownloadMarkdown={downloadMarkdown}
151167
isDownloadDisabled={!markdownContent}
152168
/>
153169
</CardHeader>

src/components/common/TextEditor/Toolbar.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
Code2,
1616
Undo,
1717
Redo,
18-
Download,
1918
ExternalLink,
2019
Minus,
2120
} from "lucide-react";
@@ -27,7 +26,7 @@ interface ToolbarProps {
2726
onAddImage: () => void;
2827
onAddImageFromFile: () => void;
2928
onAddLink: () => void;
30-
onDownloadMarkdown: () => void;
29+
// onDownloadMarkdown: () => void;
3130
isDownloadDisabled: boolean;
3231
}
3332

@@ -36,8 +35,8 @@ const Toolbar = ({
3635
onAddImage,
3736
onAddImageFromFile,
3837
onAddLink,
39-
onDownloadMarkdown,
40-
isDownloadDisabled,
38+
// onDownloadMarkdown,
39+
// isDownloadDisabled,
4140
}: ToolbarProps) => {
4241
return (
4342
<div className="flex flex-wrap items-center gap-1 rounded-lg p-1">
@@ -191,12 +190,11 @@ const Toolbar = ({
191190

192191
<Separator orientation="vertical" className="h-6" />
193192

194-
{/* Export */}
195-
<div className="flex items-center gap-1">
193+
{/* <div className="flex items-center gap-1">
196194
<ToolbarButton onClick={onDownloadMarkdown} title="Download as Markdown" disabled={isDownloadDisabled}>
197195
<Download className="h-4 w-4" />
198196
</ToolbarButton>
199-
</div>
197+
</div> */}
200198
</div>
201199
);
202200
};

src/components/common/TextEditor/utils.ts

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ import { marked } from "marked";
22
import TurndownService from "turndown";
33
import DOMPurify from "dompurify";
44

5-
// Configure Turndown for consistent markdown output
6-
const turndownService = new TurndownService();
5+
const turndownService = new TurndownService({
6+
codeBlockStyle: "fenced",
7+
headingStyle: "atx",
8+
hr: "---",
9+
bulletListMarker: "-",
10+
emDelimiter: "*",
11+
strongDelimiter: "**",
12+
});
713

8-
// Custom rule for underline tags
914
turndownService.addRule("underline", {
1015
filter: "u",
1116
replacement: (content) => `<u>${content}</u>`,
@@ -19,18 +24,11 @@ turndownService.addRule("underline", {
1924
export const markdownToHtml = (markdown: string): string => {
2025
if (!markdown) return "";
2126

22-
// If it's already HTML, sanitize and return
23-
if (markdown.includes("<") && markdown.includes(">")) {
24-
return DOMPurify.sanitize(markdown as string);
25-
}
26-
2727
try {
28-
const html = marked.parse(markdown) as string;
28+
const html = marked(markdown) as string;
2929

30-
// Sanitize HTML with DOMPurify
3130
const sanitizedHtml = DOMPurify.sanitize(html as string);
3231

33-
// Clean up HTML to be more compatible with Tiptap
3432
return sanitizedHtml;
3533
} catch (error) {
3634
console.error("Error parsing markdown:", error);
@@ -47,30 +45,11 @@ export const htmlToMarkdown = (html: string): string => {
4745
if (!html) return "";
4846

4947
try {
50-
// Sanitize HTML before converting to markdown
51-
// const sanitizedHtml = DOMPurify.sanitize(html as string);
48+
const sanitizedHtml = DOMPurify.sanitize(html as string);
5249

53-
return turndownService.turndown(html);
50+
return turndownService.turndown(sanitizedHtml);
5451
} catch (error) {
5552
console.error("Error converting HTML to markdown:", error);
5653
return html;
5754
}
5855
};
59-
60-
/**
61-
* Initialize Tiptap content from markdown
62-
* @param markdown - The markdown content
63-
* @returns HTML content ready for Tiptap editor
64-
*/
65-
export const initTiptapContent = (markdown: string): string => {
66-
return markdownToHtml(markdown);
67-
};
68-
69-
/**
70-
* Process Tiptap content to markdown for storage
71-
* @param html - The HTML content from Tiptap editor
72-
* @returns Clean markdown for storage
73-
*/
74-
export const processTiptapContent = (html: string): string => {
75-
return htmlToMarkdown(html);
76-
};

src/features/events/components/EventForm.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { Switch } from "@/components/ui/Switch";
1616
import TextEditor from "@/components/common/TextEditor/TextEditor";
1717
import { cn, generateSlug } from "@/lib/utils";
1818
import { createEventFormSchema, EventFormType } from "@/domains/Events";
19-
import { eventTypes } from "../constants";
19+
import { eventTypes, eventStatuses } from "../constants";
2020
import { useState, useEffect } from "react";
2121
import Badge from "@/components/ui/Badge";
2222

@@ -198,10 +198,11 @@ const EventForm = ({ onSubmit, isLoading = false, initialData, mode = "create" }
198198
</SelectTrigger>
199199
</FormControl>
200200
<SelectContent>
201-
<SelectItem value="coming soon">{t("options.status.coming-soon")}</SelectItem>
202-
<SelectItem value="open">{t("options.status.open")}</SelectItem>
203-
<SelectItem value="closed">{t("options.status.closed")}</SelectItem>
204-
<SelectItem value="cancelled">{t("options.status.cancelled")}</SelectItem>
201+
{eventStatuses.map((status) => (
202+
<SelectItem key={status.value} value={status.value}>
203+
{status.label}
204+
</SelectItem>
205+
))}
205206
</SelectContent>
206207
</Select>
207208
<FormMessage />

src/features/events/constants.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ export const eventTypes = [
120120
{ value: "Bootcamp", label: "Bootcamp" },
121121
];
122122

123+
export const eventStatuses = [
124+
{ value: "coming soon", label: "Coming Soon" },
125+
{ value: "open", label: "Open" },
126+
{ value: "closed", label: "Closed" },
127+
{ value: "cancelled", label: "Cancelled" },
128+
];
129+
123130
export const eventsInfo: EventInfoType[] = [
124131
{
125132
id: 1,

0 commit comments

Comments
 (0)