bae-sh / tiptap-extension-resize-image

tiptap-extension-resize-image
https://www.npmjs.com/package/tiptap-extension-resize-image
MIT License
62 stars 5 forks source link

Nextjs build fails but runs locally with image resize extension #15

Open Rchn1907 opened 5 days ago

Rchn1907 commented 5 days ago

Describe the bug Hey guys, i am encountering a different problem. My next js app is running locally fine. But when I reload the page where I use Tiptap this error occurs:

Bildschirmfoto 2024-10-04 um 15 52 35

The same Error occurs when I build my next App locally and in my CI/CD. I searched a lot of solution on the internet but could not find any possible one. I would be happy if some of you guys could help me. Here is my Code:

import React, { useEffect, useRef, useState } from 'react';
import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Bold from '@tiptap/extension-bold';
import Italic from '@tiptap/extension-italic';
import Underline from '@tiptap/extension-underline';
import Strike from '@tiptap/extension-strike';
import Link from '@tiptap/extension-link';
import Image from '@tiptap/extension-image';
import TextAlign from '@tiptap/extension-text-align';
import Heading from '@tiptap/extension-heading';
import BulletList from '@tiptap/extension-bullet-list';
import OrderedList from '@tiptap/extension-ordered-list';
import ListItem from '@tiptap/extension-list-item';
import Blockquote from '@tiptap/extension-blockquote';
import CodeBlock from '@tiptap/extension-code-block';
import Color from '@tiptap/extension-color';
import Highlight from '@tiptap/extension-highlight';
import TextStyle from '@tiptap/extension-text-style';
import HorizontalRule from '@tiptap/extension-horizontal-rule';
import TaskList from '@tiptap/extension-task-list';
import TaskItem from '@tiptap/extension-task-item';
import { Button, Box, TextField, ButtonGroup, Divider } from '@mui/material';
import authConfig from 'src/configs/auth';

import { 
  FormatBold, 
  FormatHeader1, 
  FormatHeader2, 
  FormatItalic, 
  FormatListBulleted, 
  FormatListNumbered, 
  FormatQuoteOpen, 
  FormatStrikethrough, 
  FormatUnderline, 
  CodeBraces, 
  FormatAlignLeft, 
  FormatAlignCenter, 
  FormatAlignRight, 
  ImageArea,
  FormatColorText,
  FormatColorHighlight,
  Minus,
  LinkVariant,
  LinkVariantOff,
  FormatHeader3,
  TableLargePlus,
  TableLargeRemove,
  TableRowPlusAfter,
  TableColumnPlusAfter,
  FormatHeader4
} from 'mdi-material-ui';
import toast from 'react-hot-toast';
import axios from 'axios';
import ImageResize from 'tiptap-extension-resize-image';
import Table from '@tiptap/extension-table';
import TableRow from '@tiptap/extension-table-row';
import TableCell from '@tiptap/extension-table-cell';
import TableHeader from '@tiptap/extension-table-header';

export default function TiptapEditor(props) {
  const {language, value, setValue, readOnly} = props;
  const fileInputRef = useRef(null); // Reference to the file input
  const textColorRef = useRef(null);
  const highlightColorRef = useRef(null);

  const editor = useEditor({
    extensions: [
      StarterKit,
      Bold,
      Italic,
      Underline,
      Strike,
      Link,
      Image,
      ImageResize,
      TextAlign.configure({ types: ['heading', 'paragraph'] }),
      Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
      BulletList,
      OrderedList,
      ListItem,
      Blockquote,
      CodeBlock,
      Color,
      Highlight.configure({ multicolor: true }),
      TextStyle,
      HorizontalRule,
      TaskList,
      TaskItem.configure({ nested: true }),
      Table.configure({resizable: true}),
      TableRow,
      TableCell,
      TableHeader
    ],
    content: value,
    autofocus: true,
    editable: !readOnly,
    onUpdate: ({ editor }) => {
      setValue(editor.getHTML()); // Updates the state with the HTML content
    },
  });

  const handleTextColorChange = (e) => {
    editor.chain().focus().setColor(e.target.value).run();
    textColorRef.current.style.display = 'none'; // Hide the color input
  };

  const handleHighlightColorChange = (e) => {
    editor.chain().focus().toggleHighlight({ color:  `${e.target.value}` }).run();
    highlightColorRef.current.style.display = 'none'; // Hide the highlight input
  };  

  const handleSetLink = () => {
    const url = window.prompt('Enter the link URL:');
    if (url) {
      editor.chain().focus().setLink({ href: url }).run();
    }
  };

  // Function to handle image upload
  const handleImageUpload = async (event) => {
    const file = event.target.files[0];
    if (file) {
      // Assuming you have an API endpoint to upload the image
      const formData = new FormData();
      formData.append('file', file);

      try {
        const url = `Media?languageId=${language}`;
        const res = await axios.post(authConfig.defaultEndpoint + url, formData);
        let urlEncoded = encodeURIComponent(res.data.Data.Description);
        editor.chain().focus().setImage({src: `${authConfig.defaultEndpoint}Media/${urlEncoded}`}).run();
      } catch (error) {
        toast.error("Error uploading image");
        console.error('Error uploading image:', error.response || error.message);
      }
    }
  };

  if (!editor) {
    return null;
  }

  // Toolbar for editor controls with active state check
  const Toolbar = () => (
    <Box sx={{ display: 'flex', gap: '8px', mb: 2, mt: 2, flexWrap: 'wrap'}}>
      <ButtonGroup size='small' disabled={readOnly}>
        <Button 
          onClick={() => editor.chain().focus().toggleBold().run()} 
          variant={editor.isActive('bold') ? 'contained' : 'outlined'}
        >
          <FormatBold />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleItalic().run()} 
          variant={editor.isActive('italic') ? 'contained' : 'outlined'}
        >
          <FormatItalic />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleUnderline().run()} 
          variant={editor.isActive('underline') ? 'contained' : 'outlined'}
        >
          <FormatUnderline />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleStrike().run()} 
          variant={editor.isActive('strike') ? 'contained' : 'outlined'}
        >
          <FormatStrikethrough />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleBulletList().run()} 
          variant={editor.isActive('bulletList') ? 'contained' : 'outlined'}
        >
          <FormatListBulleted />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleOrderedList().run()} 
          variant={editor.isActive('orderedList') ? 'contained' : 'outlined'}
        >
          <FormatListNumbered />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} 
          variant={editor.isActive('heading', { level: 1 }) ? 'contained' : 'outlined'}
        >
          <FormatHeader1 />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} 
          variant={editor.isActive('heading', { level: 2 }) ? 'contained' : 'outlined'}
        >
          <FormatHeader2 />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} 
          variant={editor.isActive('heading', { level: 3 }) ? 'contained' : 'outlined'}
        >
          <FormatHeader3 />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} 
          variant={editor.isActive('heading', { level: 4 }) ? 'contained' : 'outlined'}
        >
          <FormatHeader4 />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleBlockquote().run()} 
          variant={editor.isActive('blockquote') ? 'contained' : 'outlined'}
        >
          <FormatQuoteOpen />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().toggleCodeBlock().run()} 
          variant={editor.isActive('codeBlock') ? 'contained' : 'outlined'}
        >
          <CodeBraces />
        </Button>
        <Button onClick={() => editor.chain().focus().setHorizontalRule().run()} variant="outlined">
          <Minus />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().setTextAlign('left').run()} 
          variant={editor.isActive({ textAlign: 'left' }) ? 'contained' : 'outlined'}
        >
          <FormatAlignLeft />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().setTextAlign('center').run()} 
          variant={editor.isActive({ textAlign: 'center' }) ? 'contained' : 'outlined'}
        >
          <FormatAlignCenter />
        </Button>
        <Button 
          onClick={() => editor.chain().focus().setTextAlign('right').run()} 
          variant={editor.isActive({ textAlign: 'right' }) ? 'contained' : 'outlined'}
        >
          <FormatAlignRight />
        </Button>
        {/* Color picker for text color */}
        <Button onClick={() => textColorRef.current.style.display = 'block'}>
          <FormatColorText />
        </Button>
        <input
          ref={textColorRef}
          type="color"
          style={{ display: 'none' }}  // Initially hidden
          onChange={handleTextColorChange}
        />

        <Button onClick={() => highlightColorRef.current.style.display = 'block'}>
          <FormatColorHighlight />
        </Button>
        <input
          ref={highlightColorRef}
          type="color"
          style={{ display: 'none' }}  // Initially hidden
          onChange={handleHighlightColorChange}
        />
        <Button onClick={handleSetLink} variant="outlined">
          <LinkVariant/>
        </Button>
        <Button onClick={() => editor.chain().focus().unsetLink().run()} variant="outlined">
          <LinkVariantOff/>
        </Button>
        <Button onClick={() => fileInputRef.current.click()} variant="outlined">
          <ImageArea />
        </Button>
        <input
          type="file"
          accept="image/*"
          style={{ display: 'none' }} // Hidden file input
          ref={fileInputRef}
          onChange={handleImageUpload}
        />
        <Button onClick={() => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()} variant="outlined">
          <TableLargePlus/>
        </Button>
        <Button onClick={() => editor.chain().focus().addRowAfter().run()} variant="outlined">
          <TableRowPlusAfter/>
        </Button>
        <Button onClick={() => editor.chain().focus().addColumnAfter().run()} variant="outlined">
          <TableColumnPlusAfter/>
        </Button>
        <Button onClick={() => editor.chain().focus().deleteTable().run()} variant="outlined">
          <TableLargeRemove/>
        </Button>
      </ButtonGroup>
    </Box>
  );

  return (
    <Box sx={{position: "relative", width: "100%", border: "3px solid #444", padding: 2}}>
      <Toolbar/> {/* Render toolbar */}
      <Divider/>
      <EditorContent editor={editor}/>
    </Box>
  );
}

Desktop (please complete the following information):

Additional context Add any other context about the problem here.

bae-sh commented 3 days ago

Hey @Rchn1907,

Could you remove import Image from @tiptap/extension-image? It's already being imported and extended in ImageResize, so having both might cause conflicts.

Also, StarterKit includes Bold, Italic, Strike, Heading, BulletList, OrderedList, ListItem, Blockquote, and CodeBlock, so there's no need to import those separately.

Let me know if this helps!

Rchn1907 commented 3 days ago

Hey @bae-sh , thank you for the advice with the starter kit! I removed the image extension but the same error still occurs. I would be happy if you can help!