mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-22 05:34:45 +08:00
feat: use tailwindcss-typography as markdown styling
This commit is contained in:
@@ -99,7 +99,7 @@ export function AISelector({ onOpenChange }: AISelectorProps) {
|
||||
{hasCompletion && (
|
||||
<div className="flex max-h-[400px]">
|
||||
<ScrollArea>
|
||||
<div className="prose prose-sm p-2 px-4">
|
||||
<div className="prose prose-sm dark:prose-invert p-2 px-4">
|
||||
<Markdown>{completion}</Markdown>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
@@ -38,12 +38,12 @@ const extensions = [...defaultExtensions, slashCommand];
|
||||
|
||||
export interface ReportEditorProps {
|
||||
content: Content;
|
||||
onMarkdownChange?: (markdown: string) => void;
|
||||
}
|
||||
|
||||
const ReportEditor = ({ content }: ReportEditorProps) => {
|
||||
const ReportEditor = ({ content, onMarkdownChange }: ReportEditorProps) => {
|
||||
const [initialContent, setInitialContent] = useState<Content>(() => content);
|
||||
const [saveStatus, setSaveStatus] = useState("Saved");
|
||||
const [charsCount, setCharsCount] = useState();
|
||||
|
||||
const [openNode, setOpenNode] = useState(false);
|
||||
const [openColor, setOpenColor] = useState(false);
|
||||
@@ -63,17 +63,21 @@ const ReportEditor = ({ content }: ReportEditorProps) => {
|
||||
|
||||
const debouncedUpdates = useDebouncedCallback(
|
||||
async (editor: EditorInstance) => {
|
||||
const json = editor.getJSON();
|
||||
setCharsCount(editor.storage.characterCount.words());
|
||||
window.localStorage.setItem(
|
||||
"html-content",
|
||||
highlightCodeblocks(editor.getHTML()),
|
||||
);
|
||||
window.localStorage.setItem("novel-content", JSON.stringify(json));
|
||||
window.localStorage.setItem(
|
||||
"markdown",
|
||||
editor.storage.markdown.getMarkdown(),
|
||||
);
|
||||
// const json = editor.getJSON();
|
||||
// // setCharsCount(editor.storage.characterCount.words());
|
||||
// window.localStorage.setItem(
|
||||
// "html-content",
|
||||
// highlightCodeblocks(editor.getHTML()),
|
||||
// );
|
||||
// window.localStorage.setItem("novel-content", JSON.stringify(json));
|
||||
// window.localStorage.setItem(
|
||||
// "markdown",
|
||||
// editor.storage.markdown.getMarkdown(),
|
||||
// );
|
||||
if (onMarkdownChange) {
|
||||
const markdown = editor.storage.markdown.getMarkdown();
|
||||
onMarkdownChange(markdown);
|
||||
}
|
||||
setSaveStatus("Saved");
|
||||
},
|
||||
500,
|
||||
@@ -89,26 +93,12 @@ const ReportEditor = ({ content }: ReportEditorProps) => {
|
||||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<div className="absolute top-5 right-5 z-10 mb-5 flex gap-2">
|
||||
<div className="bg-accent text-muted-foreground rounded-lg px-2 py-1 text-sm">
|
||||
{saveStatus}
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
charsCount
|
||||
? "bg-accent text-muted-foreground rounded-lg px-2 py-1 text-sm"
|
||||
: "hidden"
|
||||
}
|
||||
>
|
||||
{charsCount} Words
|
||||
</div>
|
||||
</div>
|
||||
<EditorRoot>
|
||||
<EditorContent
|
||||
immediatelyRender={false}
|
||||
initialContent={initialContent as JSONContent}
|
||||
extensions={extensions}
|
||||
className="border-muted bg-background relative h-full w-full overflow-auto sm:mb-[calc(20vh)] sm:border sm:shadow-lg"
|
||||
className="border-muted relative h-full w-full"
|
||||
editorProps={{
|
||||
handleDOMEvents: {
|
||||
keydown: (_view, event) => handleCommandNavigation(event),
|
||||
|
||||
@@ -1,32 +1,18 @@
|
||||
import {
|
||||
Brain,
|
||||
CheckSquare,
|
||||
Code,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
ImageIcon,
|
||||
List,
|
||||
ListOrdered,
|
||||
MessageSquarePlus,
|
||||
Text,
|
||||
TextQuote,
|
||||
Twitter,
|
||||
Youtube,
|
||||
} from "lucide-react";
|
||||
import { Command, createSuggestionItems, renderItems } from "novel";
|
||||
import { uploadFn } from "./image-upload";
|
||||
// import { uploadFn } from "./image-upload";
|
||||
|
||||
export const suggestionItems = createSuggestionItems([
|
||||
{
|
||||
title: "Send Feedback",
|
||||
description: "Let us know how we can improve.",
|
||||
icon: <MessageSquarePlus size={18} />,
|
||||
command: ({ editor, range }) => {
|
||||
editor.chain().focus().deleteRange(range).run();
|
||||
window.open("/feedback", "_blank");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Text",
|
||||
description: "Just start typing with plain text.",
|
||||
@@ -132,83 +118,83 @@ export const suggestionItems = createSuggestionItems([
|
||||
command: ({ editor, range }) =>
|
||||
editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
|
||||
},
|
||||
{
|
||||
title: "Image",
|
||||
description: "Upload an image from your computer.",
|
||||
searchTerms: ["photo", "picture", "media"],
|
||||
icon: <ImageIcon size={18} />,
|
||||
command: ({ editor, range }) => {
|
||||
editor.chain().focus().deleteRange(range).run();
|
||||
// upload image
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.onchange = async () => {
|
||||
if (input.files?.length) {
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
const pos = editor.view.state.selection.from;
|
||||
uploadFn(file, editor.view, pos);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Youtube",
|
||||
description: "Embed a Youtube video.",
|
||||
searchTerms: ["video", "youtube", "embed"],
|
||||
icon: <Youtube size={18} />,
|
||||
command: ({ editor, range }) => {
|
||||
const videoLink = prompt("Please enter Youtube Video Link");
|
||||
//From https://regexr.com/3dj5t
|
||||
const ytRegex = new RegExp(
|
||||
/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/,
|
||||
);
|
||||
// {
|
||||
// title: "Image",
|
||||
// description: "Upload an image from your computer.",
|
||||
// searchTerms: ["photo", "picture", "media"],
|
||||
// icon: <ImageIcon size={18} />,
|
||||
// command: ({ editor, range }) => {
|
||||
// editor.chain().focus().deleteRange(range).run();
|
||||
// // upload image
|
||||
// const input = document.createElement("input");
|
||||
// input.type = "file";
|
||||
// input.accept = "image/*";
|
||||
// input.onchange = async () => {
|
||||
// if (input.files?.length) {
|
||||
// const file = input.files[0];
|
||||
// if (!file) return;
|
||||
// const pos = editor.view.state.selection.from;
|
||||
// uploadFn(file, editor.view, pos);
|
||||
// }
|
||||
// };
|
||||
// input.click();
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// title: "Youtube",
|
||||
// description: "Embed a Youtube video.",
|
||||
// searchTerms: ["video", "youtube", "embed"],
|
||||
// icon: <Youtube size={18} />,
|
||||
// command: ({ editor, range }) => {
|
||||
// const videoLink = prompt("Please enter Youtube Video Link");
|
||||
// //From https://regexr.com/3dj5t
|
||||
// const ytRegex = new RegExp(
|
||||
// /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/,
|
||||
// );
|
||||
|
||||
if (videoLink && ytRegex.test(videoLink)) {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setYoutubeVideo({
|
||||
src: videoLink,
|
||||
})
|
||||
.run();
|
||||
} else {
|
||||
if (videoLink !== null) {
|
||||
alert("Please enter a correct Youtube Video Link");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Twitter",
|
||||
description: "Embed a Tweet.",
|
||||
searchTerms: ["twitter", "embed"],
|
||||
icon: <Twitter size={18} />,
|
||||
command: ({ editor, range }) => {
|
||||
const tweetLink = prompt("Please enter Twitter Link");
|
||||
const tweetRegex = new RegExp(
|
||||
/^https?:\/\/(www\.)?x\.com\/([a-zA-Z0-9_]{1,15})(\/status\/(\d+))?(\/\S*)?$/,
|
||||
);
|
||||
// if (videoLink && ytRegex.test(videoLink)) {
|
||||
// editor
|
||||
// .chain()
|
||||
// .focus()
|
||||
// .deleteRange(range)
|
||||
// .setYoutubeVideo({
|
||||
// src: videoLink,
|
||||
// })
|
||||
// .run();
|
||||
// } else {
|
||||
// if (videoLink !== null) {
|
||||
// alert("Please enter a correct Youtube Video Link");
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// title: "Twitter",
|
||||
// description: "Embed a Tweet.",
|
||||
// searchTerms: ["twitter", "embed"],
|
||||
// icon: <Twitter size={18} />,
|
||||
// command: ({ editor, range }) => {
|
||||
// const tweetLink = prompt("Please enter Twitter Link");
|
||||
// const tweetRegex = new RegExp(
|
||||
// /^https?:\/\/(www\.)?x\.com\/([a-zA-Z0-9_]{1,15})(\/status\/(\d+))?(\/\S*)?$/,
|
||||
// );
|
||||
|
||||
if (tweetLink && tweetRegex.test(tweetLink)) {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setTweet({
|
||||
src: tweetLink,
|
||||
})
|
||||
.run();
|
||||
} else {
|
||||
if (tweetLink !== null) {
|
||||
alert("Please enter a correct Twitter Link");
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
// if (tweetLink && tweetRegex.test(tweetLink)) {
|
||||
// editor
|
||||
// .chain()
|
||||
// .focus()
|
||||
// .deleteRange(range)
|
||||
// .setTweet({
|
||||
// src: tweetLink,
|
||||
// })
|
||||
// .run();
|
||||
// } else {
|
||||
// if (tweetLink !== null) {
|
||||
// alert("Please enter a correct Twitter Link");
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
]);
|
||||
|
||||
export const slashCommand = Command.configure({
|
||||
|
||||
Reference in New Issue
Block a user