Skip to content

Commit

Permalink
refactor: replace existing editor with reactjs-tiptap-editor
Browse files Browse the repository at this point in the history
  • Loading branch information
sina-saeedi committed Dec 19, 2024
1 parent 0d22e19 commit 1986a59
Show file tree
Hide file tree
Showing 10 changed files with 5,798 additions and 1,734 deletions.
12 changes: 11 additions & 1 deletion frontend/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @type {import('next').NextConfig} */
import path from "node:path";

/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
images: {
Expand All @@ -20,6 +21,15 @@ const nextConfig = {
"@mantine/code-highlight",
],
},
webpack: (config, {isServer}) => {
if (!isServer) {
config.resolve.alias["yjs"] = path.resolve(
import.meta.dirname,
"node_modules/yjs",
);
}
return config;
},
};

export default nextConfig;
7,111 changes: 5,384 additions & 1,727 deletions frontend/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@
"highlight.js": "^11.10.0",
"html-react-parser": "^5.1.18",
"jsonwebtoken": "^9.0.2",
"katex": "^0.16.18",
"lowlight": "^3.1.0",
"next": "14.2.13",
"next": "^14.2.20",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.53.0",
"react-medium-image-zoom": "^5.2.11",
"reactjs-tiptap-editor": "^0.1.10",
"sharp": "^0.33.5",
"zod": "^3.23.8"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {forwardRef} from "react";
import {useComputedColorScheme} from "@mantine/core";
import RichTextEditor, {Editor} from "reactjs-tiptap-editor";
import {extensions} from "./extensions";
import "katex/dist/katex.min.css";
import "reactjs-tiptap-editor/style.css";

export type Ref = {
editor: Editor;
};

type Props = {
initialContent?: string;
};

export const ArticleEditor = forwardRef<Ref, Props>((props, ref) => {
const {initialContent = ""} = props;
const theme = useComputedColorScheme();

return (
<div dir="ltr">
<RichTextEditor
ref={ref}
output="html"
content={initialContent}
extensions={extensions}
useEditorOptions={{
immediatelyRender: false,
shouldRerenderOnTransaction: false,
}}
dark={theme === "dark"}
/>
</div>
);
});

ArticleEditor.displayName = "ArticleRichTextEditor";
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import {
BaseKit,
Blockquote,
Bold,
BulletList,
Clear,
Code,
CodeBlock,
Color,
ColumnActionButton,
Emoji,
FontSize,
FormatPainter,
Heading,
Highlight,
History,
HorizontalRule,
Iframe,
Image,
Indent,
Italic,
Katex,
LineHeight,
Link,
MoreMark,
OrderedList,
SlashCommand,
Strike,
Table,
TaskList,
TextAlign,
Underline,
Video,
Excalidraw,
TextDirection,
Mention,
} from "reactjs-tiptap-editor";
import {ImageUploadButton} from "./image-upload-button";

export const extensions = [
BaseKit.configure({
multiColumn: true,
placeholder: {
showOnlyCurrent: true,
},
}),
History,
TextDirection.configure({
types: ["heading", "paragraph", "blockquote", "list_item"],
directions: ["ltr", "rtl"],
defaultDirection: "rtl",
}),
FormatPainter.configure({spacer: true}),
Clear,
Heading.configure({spacer: true}),
FontSize,
Bold,
Italic,
Underline,
Strike,
MoreMark,
Katex.configure({
HTMLAttributes: {
dir: "ltr",
},
}),
Emoji,
Color.configure({spacer: true}),
Highlight,
BulletList.configure({
HTMLAttributes: {
dir: "rtl",
},
}),
OrderedList.configure({
HTMLAttributes: {
dir: "rtl",
},
}),
TextAlign.configure({
types: ["heading", "paragraph"],
spacer: true,
defaultAlignment: "ltr",
}),
Indent,
LineHeight,
TaskList.configure({
spacer: true,
taskItem: {
nested: true,
},
}),
Link,
Image.configure({
button: ({editor, extension, t}) => {
return {
component: ImageUploadButton,
componentProps: {
action: () => {},
upload: extension.options.upload,
disabled: !editor.can().setImage,
icon: "ImageUp",
tooltip: t("editor.image.tooltip"),
editor,
},
};
},
}).extend({
parseHTML() {
return [
{
tag: "img",
getAttrs: (img) => {
const element = img?.parentElement;

const width = img?.getAttribute("width");

const flipX = img?.getAttribute("flipx") || false;
const flipY = img?.getAttribute("flipy") || false;

return {
src: img?.getAttribute("src"),
alt: img?.getAttribute("alt"),
caption: img?.getAttribute("caption"),
width: width ? Number.parseInt(width, 10) : null,
align:
img?.getAttribute("align") || element?.style?.textAlign || null,
inline: img?.getAttribute("inline") || false,
flipX: flipX === "true",
flipY: flipY === "true",
};
},
},
{
tag: "span.image img",
getAttrs: (img) => {
const element = img?.parentElement;

const width = img?.getAttribute("width");

const flipX = img?.getAttribute("flipx") || false;
const flipY = img?.getAttribute("flipy") || false;

return {
src: img?.getAttribute("src"),
alt: img?.getAttribute("alt"),
caption: img?.getAttribute("caption"),
width: width ? Number.parseInt(width, 10) : null,
align:
img?.getAttribute("align") || element?.style?.textAlign || null,
inline: img?.getAttribute("inline") || false,
flipX: flipX === "true",
flipY: flipY === "true",
};
},
},
{
tag: "div[class=image]",
getAttrs: (element) => {
const img = element.querySelector("img");

const width = img?.getAttribute("width");
const flipX = img?.getAttribute("flipx") || false;
const flipY = img?.getAttribute("flipy") || false;

return {
src: img?.getAttribute("src"),
alt: img?.getAttribute("alt"),
caption: img?.getAttribute("caption"),
width: width ? Number.parseInt(width, 10) : null,
align:
img?.getAttribute("align") || element.style.textAlign || null,
inline: img?.getAttribute("inline") || false,
flipX: flipX === "true",
flipY: flipY === "true",
};
},
},
];
},
}),
Video.configure({
upload: (files: File) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(URL.createObjectURL(files));
}, 500);
});
},
}),
Blockquote.configure({spacer: true}),
SlashCommand,
HorizontalRule,
Code.configure({
toolbar: false,
}),
CodeBlock.configure({defaultTheme: "dracula"}),
ColumnActionButton,
Table,
Iframe,
Excalidraw,
Mention,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.image {
border: 1px solid
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
padding: 5px;
max-width: 100%;
border-radius: var(--mantine-radius-sm);
object-fit: cover;
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {useState} from "react";
import {Box, Paper, Modal, Stack, Image, Button} from "@mantine/core";
import {FilesExplorer} from "@/components/files-explorer";
import {IconPhotoPlus} from "@tabler/icons-react";
import {FILES_PUBLIC_URL} from "@/constants";
import classes from "./file-input.module.css";

export function FileInput() {
const [isFileExplorerOpen, setIsFileExplorerOpen] = useState(false);
const [selectedFile, setSelectedFile] = useState("");

const handleOpenFileExplorer = () => {
setIsFileExplorerOpen(true);
};

const handleSelectFile = (fileId: string | undefined) => {
setIsFileExplorerOpen(false);
setSelectedFile(fileId || "");
};

const handleCloseFileExplorer = () => {
setIsFileExplorerOpen(false);
};

return (
<Box>
<Stack gap="xs" align="center">
{selectedFile ? (
<Image
src={`${FILES_PUBLIC_URL}/${selectedFile}`}
alt="article's image"
className={classes.image}
onClick={handleOpenFileExplorer}
/>
) : (
<Button
variant="light"
color="gray"
size="xl"
fullWidth
onClick={handleOpenFileExplorer}
>
<IconPhotoPlus />
</Button>
)}
</Stack>
<input name="image" value={selectedFile} hidden readOnly />
<Modal
size="xl"
opened={isFileExplorerOpen}
onClose={handleCloseFileExplorer}
withCloseButton={false}
>
<Paper>
<FilesExplorer onSelect={handleSelectFile} />
</Paper>
</Modal>
</Box>
);
}
Loading

0 comments on commit 1986a59

Please sign in to comment.