Skip to content

Adding Custom ProseMirror Plugins

This guide shows how to add custom ProseMirror plugins to the SciFlow editor.

The recommended way to add plugins is through the Feature system:

import { Plugin, PluginKey } from 'prosemirror-state';
import type { Feature } from '@sciflow/editor-core';

// Create your plugin
const myPluginKey = new PluginKey('my-plugin');
const myPlugin = new Plugin({
  key: myPluginKey,
  state: {
    init: () => ({ /* initial state */ }),
    apply: (tr, state) => { /* update state */ }
  }
});

// Wrap it in a Feature
const myFeature: Feature = {
  name: 'my-custom-feature',
  addPlugins() {
    return [myPlugin];
  }
};

// Add to editor (prefer configureFeatures when available)
if (typeof editor.configureFeatures === 'function') {
  await editor.configureFeatures([citationFeature, figureFeature, myFeature]);
} else {
  editor.features = [citationFeature, figureFeature, myFeature];
}

Accessing Plugin State

Use the stable plugin API to read and update plugin state:

// Read plugin state
const state = editor.plugins.getState(myPluginKey);

// Update plugin state via meta
editor.plugins.dispatchMeta(myPluginKey, {
  action: 'update',
  data: { /* your data */ }
});

Document Traversal

Access the document structure to find positions:

const doc = editor.document;
if (doc) {
  doc.descendants((node, pos) => {
    if (node.type.name === 'paragraph') {
      console.log('Found paragraph at', pos);
    }
  });
}

Position Utilities

Map document positions to screen coordinates:

// Get coordinates for a position
const coords = editor.positions.coordsAtPos(100);
if (coords) {
  console.log('Position 100 is at', coords.top, coords.left);
}

// Resolve position for context
const resolved = editor.positions.resolve(100);
if (resolved) {
  console.log('Parent node:', resolved.parent);
  console.log('Depth:', resolved.depth);
}

Styling Plugin Decorations

If your plugin creates ProseMirror decorations (e.g., highlights, annotations, or inline widgets), inject CSS into the editor’s Shadow DOM. Prefer setShadowStyles() for immediate updates after installing a plugin. See Web Components Basics for the full styling API and examples.

Custom Citation Source Editor

The selection editor’s citation adapter is pluggable. Use a custom adapter to drive your own UI:

import { setCitationSourceAdapter, SourceField } from '@sciflow/editor-start';

setCitationSourceAdapter({
  render(container, value, context) {
    // Build your UI and call context.applySource(SourceField.toString(items)) on save
  },
  update(container, value, context) { /* optional */ },
  destroy(container) { /* optional */ },
});

You can also set selectionEditor.citationSourceAdapter per element instance for one-off overrides.