= ({
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);
- }}
- >
+ }}>
↗
= ({
{
"novel-text-blue-500": editor.isActive("link"),
}
- )}
- >
+ )}>
Link
@@ -51,8 +49,7 @@ export const LinkSelector: FC = ({
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">
= ({
onClick={() => {
editor.chain().focus().unsetLink().run();
setIsOpen(false);
- }}
- >
+ }}>
) : (
diff --git a/packages/core/src/ui/editor/extensions/slash-command.tsx b/packages/core/src/ui/editor/extensions/slash-command.tsx
index 3ea919b..fe7f426 100644
--- a/packages/core/src/ui/editor/extensions/slash-command.tsx
+++ b/packages/core/src/ui/editor/extensions/slash-command.tsx
@@ -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: ,
+ searchTerms: ["photo", "picture", "media", "img"],
+ icon: ,
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: ,
+ 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.",
diff --git a/packages/core/src/ui/editor/plugins/upload-images.tsx b/packages/core/src/ui/editor/plugins/upload-images.tsx
index 2249b44..582f2f2 100644
--- a/packages/core/src/ui/editor/plugins/upload-images.tsx
+++ b/packages/core/src/ui/editor/plugins/upload-images.tsx
@@ -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."
);