feat: suppot remote image url
This commit is contained in:
@@ -10,8 +10,6 @@ import {
|
||||
ListPlus,
|
||||
PartyPopper,
|
||||
PauseCircle,
|
||||
Pipette,
|
||||
Repeat,
|
||||
Scissors,
|
||||
Wand,
|
||||
} from "lucide-react";
|
||||
|
@@ -28,8 +28,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({
|
||||
className="novel-flex novel-h-full novel-items-center novel-space-x-2 novel-px-3 novel-py-1.5 novel-text-sm novel-font-medium novel-text-stone-600 hover:novel-bg-stone-100 active:novel-bg-stone-200"
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen);
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<p className="novel-text-base">↗</p>
|
||||
<p
|
||||
className={cn(
|
||||
@@ -37,8 +36,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({
|
||||
{
|
||||
"novel-text-blue-500": editor.isActive("link"),
|
||||
}
|
||||
)}
|
||||
>
|
||||
)}>
|
||||
Link
|
||||
</p>
|
||||
</button>
|
||||
@@ -51,8 +49,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({
|
||||
url && editor.chain().focus().setLink({ href: url }).run();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
className="novel-fixed novel-top-full novel-z-[99999] novel-mt-1 novel-flex novel-w-60 novel-overflow-hidden novel-rounded novel-border novel-border-stone-200 novel-bg-white novel-p-1 novel-shadow-xl novel-animate-in novel-fade-in novel-slide-in-from-top-1"
|
||||
>
|
||||
className="novel-fixed novel-top-full novel-z-[99999] novel-mt-1 novel-flex novel-w-60 novel-overflow-hidden novel-rounded novel-border novel-border-stone-200 novel-bg-white novel-p-1 novel-shadow-xl novel-animate-in novel-fade-in novel-slide-in-from-top-1">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
@@ -67,8 +64,7 @@ export const LinkSelector: FC<LinkSelectorProps> = ({
|
||||
onClick={() => {
|
||||
editor.chain().focus().unsetLink().run();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<Trash className="novel-h-4 novel-w-4" />
|
||||
</button>
|
||||
) : (
|
||||
|
@@ -26,6 +26,7 @@ import {
|
||||
CheckSquare,
|
||||
Table2,
|
||||
PauseCircle,
|
||||
FileImage,
|
||||
} from "lucide-react";
|
||||
import { LoadingCircle } from "@/ui/icons";
|
||||
import { toast } from "sonner";
|
||||
@@ -35,6 +36,7 @@ import { getPrevText } from "@/lib/editor";
|
||||
import { startImageUpload } from "@/ui/editor/plugins/upload-images";
|
||||
import { NovelContext } from "../provider";
|
||||
import { Youtube } from "lucide-react";
|
||||
import { isImageLink } from "@/lib/utils";
|
||||
|
||||
interface CommandItemProps {
|
||||
title: string;
|
||||
@@ -211,10 +213,10 @@ const getSuggestionItems = ({
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Image",
|
||||
title: "Local image",
|
||||
description: "Upload an image from your computer.",
|
||||
searchTerms: ["photo", "picture", "media"],
|
||||
icon: <ImageIcon size={18} />,
|
||||
searchTerms: ["photo", "picture", "media", "img"],
|
||||
icon: <FileImage size={18} />,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
editor.chain().focus().deleteRange(range).run();
|
||||
// upload image
|
||||
@@ -231,6 +233,25 @@ const getSuggestionItems = ({
|
||||
input.click();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Remote image",
|
||||
description: "Render an image from url.",
|
||||
searchTerms: ["photo", "picture", "media", "img"],
|
||||
icon: <ImageIcon size={18} />,
|
||||
command: ({ editor, range }: CommandProps) => {
|
||||
const url = prompt("Enter image url");
|
||||
if (url && isImageLink(url)) {
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange(range)
|
||||
.setImage({
|
||||
src: url,
|
||||
})
|
||||
.run();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Youtube video",
|
||||
description: "Play the Youtube video you filled out.",
|
||||
|
@@ -2,8 +2,6 @@ import { BlobResult } from "@vercel/blob";
|
||||
import { toast } from "sonner";
|
||||
import { EditorState, Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { Decoration, DecorationSet, EditorView } from "@tiptap/pm/view";
|
||||
import { NovelContext } from "../provider";
|
||||
import { useContext } from "react";
|
||||
|
||||
const uploadKey = new PluginKey("upload-image");
|
||||
|
||||
@@ -26,7 +24,7 @@ const UploadImagesPlugin = () =>
|
||||
const image = document.createElement("img");
|
||||
image.setAttribute(
|
||||
"class",
|
||||
"opacity-40 rounded-lg border border-stone-200"
|
||||
"opacity-40 rounded-md border border-stone-200"
|
||||
);
|
||||
image.src = src;
|
||||
placeholder.appendChild(image);
|
||||
@@ -62,8 +60,8 @@ export function startImageUpload(file: File, view: EditorView, pos: number) {
|
||||
if (!file.type.includes("image/")) {
|
||||
toast.error("File type not supported.");
|
||||
return;
|
||||
} else if (file.size / 1024 / 1024 > 5) {
|
||||
toast.error(`File size too big (max ${15}MB).`);
|
||||
} else if (file.size / 1024 / 1024 > 1) {
|
||||
toast.error(`File size too big (max ${1}MB).`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,7 +109,6 @@ export function startImageUpload(file: File, view: EditorView, pos: number) {
|
||||
}
|
||||
|
||||
export const handleImageUpload = (file: File) => {
|
||||
// upload to Vercel Blob
|
||||
return new Promise((resolve) => {
|
||||
toast.promise(
|
||||
fetch("/api/upload", {
|
||||
@@ -125,6 +122,7 @@ export const handleImageUpload = (file: File) => {
|
||||
// Successfully uploaded image
|
||||
if (res.status === 200) {
|
||||
const { url } = (await res.json()) as BlobResult;
|
||||
|
||||
// preload the image
|
||||
let image = new Image();
|
||||
image.src = url;
|
||||
@@ -134,7 +132,6 @@ export const handleImageUpload = (file: File) => {
|
||||
// No blob store configured
|
||||
} else if (res.status === 401) {
|
||||
resolve(file);
|
||||
|
||||
throw new Error(
|
||||
"`BLOB_READ_WRITE_TOKEN` environment variable not found, reading image locally instead."
|
||||
);
|
||||
|
Reference in New Issue
Block a user