Enhance your images with the Tiptap extension for customizable captions

click here for image description

I am looking to include an image along with an editable caption using the tiptap extension

Check out this link for more information
I found a great example with ProseMirror, but I'm wondering if it's possible to achieve the same with tiptap.
Can someone guide me on what code needs to be written?

Below is the code snippet that I have worked on.
The image and caption are successfully displayed, but the caption is not yet editable.

// ./CustomImage.ts
// @ts-ignore
import { Node, Plugin } from 'tiptap'
// @ts-ignore
import { nodeInputRule } from 'tiptap-commands'

const IMAGE_INPUT_REGEX = /!\[(.+|:?)\]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/

export default class CustomImage extends Node {
  get name () {
    return 'customImage'
  }

  get schema () {
    return {
      attrs: {
        src: {
          default: null
        },
        alt: {
          default: null
        },
        title: {
          default: null
        },
        caption: {
          default: null
        }
      },
      group: 'block',
      selectable: false,
      draggable: true,
      parseDOM: [
        {
          tag: 'figure'
        },
        [
          {
            tag: 'img[src]',
            getAttrs: (dom: any) => ({
              src: dom.getAttribute('src'),
              title: dom.getAttribute('title'),
              alt: dom.getAttribute('alt')
            })
          },
          {
            tag: 'figcaption'
          }
        ]
      ],
      toDOM: (node: any) => [
        'figure',
        [
          'img',
          {
            src: node.attrs.src,
            title: node.attrs.title,
            alt: node.attrs.alt
          }
        ],
        [
          'figcaption',
          {
            contenteditable: 'true'
          },
          node.attrs.caption
        ]
      ]
    }
  }

  commands ({ type }: any) {
    return (attrs: any) => (state: any, dispatch: any) => {
      const { selection } = state
      const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos
      const node = type.create(attrs)
      const transaction = state.tr.insert(position, node)
      dispatch(transaction)
    }
  }

  inputRules ({ type }: any) {
    return [
      nodeInputRule(IMAGE_INPUT_REGEX, type, (match: any) => {
        const [, alt, src, title] = match
        return {
          src,
          alt,
          title
        }
      })
    ]
  }

  get plugins () {
    return [
      new Plugin({
        props: {
          handleDOMEvents: {
            drop (view: any, event: any) {
              const hasFiles = event.dataTransfer &&
              event.dataTransfer.files &&
              event.dataTransfer.files.length

              if (!hasFiles) {
                return
              }

              const images = Array
                .from(event.dataTransfer.files)
                .filter((file: any) => (/image/i).test(file.type))

              if (images.length === 0) {
                return
              }

              event.preventDefault()

              const { schema } = view.state
              const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY })

              images.forEach((image: any) => {
                const reader = new FileReader()

                reader.onload = (readerEvent: any) => {
                  const node = schema.nodes.image.create({
                    src: readerEvent.target.result
                  })
                  const transaction = view.state.tr.insert(coordinates.pos, node)
                  view.dispatch(transaction)
                }
                reader.readAsDataURL(image)
              })
            }
          }
        }
      })
    ]
  }
}

Answer №1

There seems to be a missing "hole" in the figure caption that allows for editing. For more information, refer to . As per suggestions from the ProseMirror thread, I have implemented one of the solutions. However, text can still be entered between the image and figure caption. It might be beneficial to consider using the last example with 3 schemas instead.

  get schema() {
    return {
      content: "inline*",
      attrs: {src: {default: ""}, title: {default: ""}},
      group: 'block',
      draggable: true,
      isolating: true,
      parseDOM: [{
        tag: "figure",
        contentElement: "figcaption",
        getAttrs(dom) {
          let img = dom.querySelector("img")
          console.log(img, img.getAttribute('src'), img.getAttribute('alt'));
          return {src: img.getAttribute('src'), title: img.getAttribute('alt')}
        }
      }],
      toDOM: node => ["figure", ["img", node.attrs], ["figurecaption", 0]],
    }
  }

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Implementing a NestJs application on a microcomputer like a Raspberry Pi or equivalent device

I'm facing a challenge in trying to find a solution for what seems like a simple task. I am aware that using the Nest CLI, I can utilize the command "nest build" to generate a dist folder containing the production files of my project. However, when I ...

Quasar version 2 (Vue) introduces an exciting new feature in the QTable component - the ability to

Could someone provide some guidance on how to create a table with nested elements like the one shown in the screenshot? View table image here I am facing an issue connecting additional drop-down lines within the table. Is there functionality available fo ...

Store the active tab in AngularJS with Bootstrap to easily remember and display

After creating a basic AngularJS application with the Bootstrap directive, I noticed that some of my pages have tabs. The issue arises when I am on a tab other than the first one and click a link to navigate to another view. Upon returning (using either th ...

Knockout.js client-side validation system

Currently working on a sophisticated client-side web application and I'm in search of the perfect framework for handling validations. I experimented with JQuery validation plugin, but unfortunately it didn't integrate smoothly with knockout.js da ...

The properties in Typescript, specifically 'value', are not compatible with each other

I've encountered an error while working on a TypeScript project with React Context. The error message states: Argument of type 'Context<{}>' is not assignable to parameter of type 'Context<IsProductContext>'. The type ...

Exploring the versatility of Vue.js through props and scoped slots

Coming from a React background, I am used to being able to easily alter children components before they render. However, after spending hours reading the VueJS documentation and searching forums, I have not been able to find a straightforward way to do thi ...

Next.js version 14 is having difficulties displaying the loading.tsx file

click here for image description Why is the loading not displaying when navigating to /profile? How can I fix this issue? export default function Loading() { // You can add any UI inside Loading, including a Skeleton. return ( <div> lo ...

PhantomJS Karma encountering SyntaxError when trying to export variables

I've encountered an issue while running Karma and PhantomJS. When I attempt to run, the console displays the following message: 22 03 2016 14:58:47.865:WARN [karma]: No captured browser, open http://localhost:9876/ 22 03 2016 14:58:47.875:INFO [karm ...

Creating a CSS triangle that smoothly transitions between two different colors

Is it possible to create a triangle in CSS that smoothly transitions between two colors without relying on a hover state? .arrow-down { width: 0; height: 0; border-left: 20px solid transparent; border-right: 20px solid transparent; b ...

What is the reason for having the navigation bar stretch across the entire width of the mobile device?

NEW QUESTION: How Can I Fix the Navigation Bar Issue on Mobile Devices? I apologize for any language issues as I am using a translator. Hello, I have encountered an issue with a website form containing a price list. When viewed on a mobile device, all the ...

Is there a way to set up automatic switching to a minimized browser window when receiving an alert, even if you are currently using a different window like Outlook or Explorer?

Is there a way to automatically switch to a minimized browser window from a different program window (such as Outlook or Explorer) when an alert is received on a specific tab? I'm looking for a Javascript/Jquery solution. I've attempted the foll ...

Ascending to the Peak within a div

<script type="text/javascript"> $(document).ready(function(){ updateContent(); }); function updateContent(){ $('#mainDiv').load('home.php', function(){ scrollToTop(); }); } ...

The JSON request was unsuccessful in sending the JSON object to PHP, resulting in an empty var_dump

I am encountering an issue where my $_POST array in PHP is empty when I try to pass a JSON object with an AJAX request on my JavaScript page. Here is the JavaScript code: function send(cat, subcat) { var type = cat.options[cat.selectedIndex].text ...

Converting a Jquery object into a Javascript object

Can someone help me decipher this piece of code? //... obj.dd.on('click', function(event){ $(this).toggleClass('active'); return false; }); //... $(function() { var dd = new DropDown( $('#dd') ); $(doc ...

Ensure that the link's color remains constant and remove any styling of text-decoration

Attempting to create a customized header. My goal is to change the color and remove text decoration in the navbar. Below is my code snippet: ReactJS: import React from 'react'; import './Header.css'; function Header() { return ( ...

A scenario in a Jasmine test where a function is invoked within an if statement

My coding dilemma involves a function: function retrieveNames() { var identifiers = []; var verifyAttribute = function (array, attr, value) { for (var i = 0; i < array.length; i++) { if (array[i][attr] === va ...

Alter the hue within Google Chart

I'm working on customizing a Google Bar Chart with Material design and running into an issue with changing the background color. I've tried using backgroundColor and fill identifiers in the options, but the inner area of the chart where the data ...

Nextjs version 13 encountered hydration failure due to discrepancies between the initial UI and the server-rendered content

I am currently utilizing the latest version 13.1.0. I have implemented a ContextProvider that allows switching between light and dark themes. 'use client'; import { Theme, ThemeContext } from '@store/theme'; import { ReactNode, useState ...

Is there a way to determine the number of syllables in text as it is being typed?

Working on a React-based web app, I am trying to determine the number of syllables in a textarea as the user types. Encountering errors like "cannot find length of a null" at every turn. Right now, all I want is to utilize console.log() for troubleshooti ...

Gulp encountered an issue - TypeError: When attempting to call the 'match' method, it was found to be undefined

Currently, I'm attempting to utilize Gulp alongside BrowserSync for a website that is being hosted on MAMP and proxied through localhost:8888. Unfortunately, upon running gulp, I encounter the following error: [17:38:48] Starting 'browser-sync& ...