ProseMirror Resizable View

3.0.0 · active · verified Sun Apr 19

The `prosemirror-resizable-view` package provides a specialized `NodeView` implementation for ProseMirror that enables users to resize custom nodes directly within the editor interface. It is an integral part of the broader Remirror ecosystem and currently aligns with Remirror's stable version 3.0.0. This package itself is at version 3.0.0, benefiting from the active development cadence of the Remirror project, which includes regular patch releases for bug fixes and dependency updates. Its primary differentiator lies in abstracting the complexities of implementing resizable DOM elements within a ProseMirror `NodeView`, offering a convenient base class that handles drag events and dimension management. This significantly simplifies the development process for integrating dynamic resize functionality for various content types like images, video embeds, or custom block components. The package comes with comprehensive TypeScript type definitions, enhancing developer experience and code safety.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to create a basic ProseMirror editor with a custom `image` node, then renders this node using `ResizableImageView` to allow users to resize images within the editor. It includes schema definition, `ResizableNodeView` extension, and editor setup.

import { ResizableNodeView } from 'prosemirror-resizable-view';
import { Node as ProsemirrorNode, Schema } from 'prosemirror-model';
import { EditorView, NodeView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
import { baseKeymap } from 'prosemirror-commands';
import { keymap } from 'prosemirror-keymap';

// 1. Define a basic schema with an 'image' node that can have width/height attributes
const mySchema = new Schema({
  nodes: {
    doc: { content: 'block+' },
    paragraph: { content: 'inline*', group: 'block' },
    image: {
      inline: false,
      attrs: {
        src: { default: '' },
        alt: { default: null },
        title: { default: null },
        width: { default: null },
        height: { default: null },
      },
      group: 'block',
      parseDOM: [{
        tag: 'img[src]',
        getAttrs(dom) {
          if (typeof dom === 'string') return {};
          return {
            src: dom.getAttribute('src'),
            alt: dom.getAttribute('alt'),
            title: dom.getAttribute('title'),
            width: dom.getAttribute('width'),
            height: dom.getAttribute('height'),
          };
        },
      }],
      toDOM(node) {
        return [
          'img',
          {
            src: node.attrs.src,
            alt: node.attrs.alt,
            title: node.attrs.title,
            width: node.attrs.width, // Render initial width
            height: node.attrs.height, // Render initial height
          },
        ];
      },
    },
    text: { inline: true, group: 'inline' },
  },
  marks: {},
});

// 2. Helper function to create the actual DOM element for the image
const createInnerImage = ({ node }: { node: ProsemirrorNode }) => {
  const inner = document.createElement('img');
  inner.setAttribute('src', node.attrs.src);
  if (node.attrs.alt) inner.setAttribute('alt', node.attrs.alt);
  if (node.attrs.title) inner.setAttribute('title', node.attrs.title);
  inner.style.width = node.attrs.width ? `${node.attrs.width}px` : '100%';
  inner.style.height = node.attrs.height ? `${node.attrs.height}px` : 'auto';
  inner.style.minWidth = '50px';
  inner.style.objectFit = 'contain';
  return inner;
};

// 3. Extend ResizableNodeView to create a custom resizable image view
export class ResizableImageView extends ResizableNodeView implements NodeView {
  constructor(node: ProsemirrorNode, view: EditorView, getPos: () => number) {
    super({
      node,
      view,
      getPos,
      createElement: createInnerImage,
      // Optional: Set aspectRatio to 'lock' to maintain aspect ratio, or 'free'.
      // aspectRatio: 'lock',
      updateSize: (width, height) => {
        // This callback is crucial: dispatch a transaction to update the node's attributes
        const tr = view.state.tr.setNodeMarkup(getPos(), undefined, {
          ...node.attrs,
          width,
          height,
        });
        view.dispatch(tr);
      },
    });
  }
}

// 4. Set up the ProseMirror Editor
const editorDiv = document.createElement('div');
editorDiv.id = 'editor';
document.body.appendChild(editorDiv);

const state = EditorState.create({
  schema: mySchema,
  doc: mySchema.nodeFromJSON({
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [{ type: 'text', text: 'Drag the handles to resize the image:' }],
      },
      {
        type: 'image',
        attrs: { src: 'https://via.placeholder.com/250x180', width: 250, height: 180 },
      },
      {
        type: 'paragraph',
        content: [{ type: 'text', text: 'This is some text below the resizable image.' }],
      },
    ],
  }),
  plugins: [keymap(baseKeymap)],
});

const view = new EditorView(editorDiv, {
  state,
  nodeViews: {
    image(node, view, getPos) {
      return new ResizableImageView(node, view, getPos);
    },
  },
});

// Expose view for debugging in browser console
(window as any).view = view;

view raw JSON →