Skip to content

Latest commit

 

History

History
296 lines (253 loc) · 6.64 KB

File metadata and controls

296 lines (253 loc) · 6.64 KB

JSON RTE Plugin Development Guide

Quick reference for creating JSON Rich Text Editor plugins using the new simplified approach.

🚀 Quick Start

import ContentstackAppSDK, { PluginBuilder } from '@contentstack/app-sdk';

// Create a simple plugin
const boldPlugin = new PluginBuilder('bold-plugin')
  .title('Bold')
  .elementType('inline')
  .on('exec', (rte) => {
    rte.addMark('bold', true);
  })
  .build();

// Register the plugin
ContentstackAppSDK.registerRTEPlugins(boldPlugin);

📋 Plugin Types

Inline Plugin

For text formatting (bold, italic, etc.)

const italicPlugin = new PluginBuilder('italic')
  .title('Italic')
  .elementType('inline')
  .display(['toolbar', 'hoveringToolbar'])
  .on('exec', (rte) => {
    rte.addMark('italic', true);
  })
  .build();

Block Plugin

For block-level elements (headings, paragraphs, etc.)

const headingPlugin = new PluginBuilder('heading')
  .title('Heading')
  .elementType('block')
  .render(({ children, attrs }) => (
    <h2 style={{ color: attrs.color || 'black' }}>
      {children}
    </h2>
  ))
  .on('exec', (rte) => {
    rte.insertNode({
      type: 'heading',
      attrs: { level: 2 },
      children: [{ text: 'New Heading' }]
    });
  })
  .build();

Void Plugin

For self-closing elements (images, embeds, etc.)

const imagePlugin = new PluginBuilder('image')
  .title('Image')
  .elementType('void')
  .render(({ attrs }) => (
    <img 
      src={attrs.src} 
      alt={attrs.alt || 'Image'} 
      style={{ maxWidth: '100%' }}
    />
  ))
  .on('exec', (rte) => {
    const src = prompt('Enter image URL:');
    if (src) {
      rte.insertNode({
        type: 'image',
        attrs: { src },
        children: [{ text: '' }]
      });
    }
  })
  .build();

🎛️ Builder Methods

Basic Configuration

new PluginBuilder('plugin-id')
  .title('Plugin Name')           // Toolbar button text
  .icon(<CustomIcon />)           // Button icon (React element)
  .elementType('block')           // 'inline' | 'block' | 'void'

Display Options

  .display(['toolbar'])                    // Show in main toolbar only
  .display(['hoveringToolbar'])           // Show in hover toolbar only  
  .display(['toolbar', 'hoveringToolbar']) // Show in both

Event Handlers

  .on('exec', (rte) => {})              // Button click
  .on('keydown', ({ event, rte }) => {}) // Key press
  .on('paste', ({ rte, preventDefault }) => {}) // Paste event

Advanced Options

  .render(ComponentFunction)              // Custom render component
  .shouldOverride((element) => boolean)   // Override existing elements
  .configure(async (sdk) => {})          // Dynamic configuration

🔧 Event Handling

Click Handler

.on('exec', (rte) => {
  // Insert text
  rte.insertText('Hello World');
  
  // Add formatting
  rte.addMark('bold', true);
  
  // Insert node
  rte.insertNode({
    type: 'custom-element',
    attrs: { id: 'unique-id' },
    children: [{ text: 'Content' }]
  });
})

Keyboard Handler

.on('keydown', ({ event, rte }) => {
  if (event.key === 'Enter' && event.ctrlKey) {
    event.preventDefault();
    // Custom enter behavior
    rte.insertBreak();
  }
})

📦 Container Plugins (Dropdowns)

Create grouped plugins in a dropdown menu:

const mediaContainer = new PluginBuilder('media-dropdown')
  .title('Media')
  .icon(<MediaIcon />)
  .addPlugins(
    imagePlugin,
    videoPlugin,
    audioPlugin
  )
  .build();

🔄 Plugin Registration

Single Plugin

ContentstackAppSDK.registerRTEPlugins(myPlugin);

Multiple Plugins

ContentstackAppSDK.registerRTEPlugins(
  boldPlugin,
  italicPlugin,
  headingPlugin,
  imagePlugin
);

With Enhanced SDK Context

// Register plugins first (captures RTE context)
await ContentstackAppSDK.registerRTEPlugins(myPlugin);

// Then initialize SDK (gets enhanced context)
const sdk = await ContentstackAppSDK.init();

💡 Real-World Examples

YouTube Embed Plugin

const youtubePlugin = new PluginBuilder('youtube')
  .title('YouTube')
  .elementType('void')
  .render(({ attrs }) => (
    <iframe
      width="560"
      height="315"
      src={`https://www.youtube.com/embed/${attrs.videoId}`}
      frameBorder="0"
      allowFullScreen
    />
  ))
  .on('exec', (rte) => {
    const url = prompt('Enter YouTube URL:');
    const videoId = extractVideoId(url);
    if (videoId) {
      rte.insertNode({
        type: 'youtube',
        attrs: { videoId },
        children: [{ text: '' }]
      });
    }
  })
  .build();

Smart Quote Plugin

const smartQuotePlugin = new PluginBuilder('smart-quote')
  .title('Smart Quotes')
  .elementType('inline')
  .on('keydown', ({ event, rte }) => {
    if (event.key === '"') {
      event.preventDefault();
      const isStart = rte.selection.isAtStart();
      rte.insertText(isStart ? '"' : '"');
    }
  })
  .build();

Dynamic Configuration Plugin

const configurablePlugin = new PluginBuilder('configurable')
  .title('Dynamic Plugin')
  .configure(async (sdk) => {
    const config = await sdk.getConfig();
    return {
      title: config.customTitle || 'Default Title',
      icon: config.customIcon || <DefaultIcon />
    };
  })
  .on('exec', (rte) => {
    // Plugin logic using dynamic config
  })
  .build();

🎯 Best Practices

  1. Use semantic IDs: 'heading-h2' instead of 'plugin1'
  2. Provide clear titles: Users see these in the toolbar
  3. Handle edge cases: Check for selection, validate inputs
  4. Use TypeScript: Better development experience
  5. Test thoroughly: Different content structures, browser compatibility

📚 Migration from Legacy

Old Way (Legacy RTEPlugin)

const oldPlugin = new RTEPlugin('my-plugin', (rte) => ({
  title: 'My Plugin',
  icon: <Icon />,
  display: ['toolbar'],
  elementType: ['block'],
  render: MyComponent
}));
oldPlugin.on('exec', handler);

New Way (PluginBuilder)

const newPlugin = new PluginBuilder('my-plugin')
  .title('My Plugin')
  .icon(<Icon />)
  .display(['toolbar'])
  .elementType('block')
  .render(MyComponent)
  .on('exec', handler)
  .build();

🔗 Resources


Happy plugin building! 🚀