By writing services and starting them through the config, you extend the funcionality of the editor.
All service providers extend the Service class. Most service providers contain a register and a boot method. Within the register method, you should only bind things into the service container.
Let’s take a look at a simple service like the StrongService. Within any of your service provider methods, you always have access to the config and schema properties and also you have access to the service container using inversify.io.
import { Service } from 'wax-prosemirror-core';
import { toggleMark } from 'prosemirror-commands';
import strongMark from './schema/strongMark';
import Strong from './Strong';
import './strong.css';
class StrongService extends Service {
register() {
this.container.bind('Strong').to(Strong);
const createMark = this.container.get('CreateMark');
const CreateShortCut = this.container.get('CreateShortCut');
createMark({
strong: strongMark,
});
CreateShortCut({
'Mod-b': toggleMark(this.schema.marks.strong),
});
}
}
export default StrongService;
This service provider defines a register method, it registers a class and in this case is the strong tool. For more information on how to use service container check inversify.io documentation.
So, what if we need to register a view component within our service provider? This should be done within the boot method. This method is called after all other service providers have been registered, meaning you have access to all other services that have been registered. A representative example could be the MenuSerivce
boot() {
if (this.app.config.get('config.MenuService') === undefined) return false;
const { menus } = this.container.get('MenuCollection');
const layout = this.container.get('Layout');
menus.forEach(menu => {
layout.addComponent(menu.config.templateArea, menu.render());
});
}
In Menu’s boot method we get Layout and we add components to the already defined areas from our Layout.
Use createNone and createMark to register a new node or mark
register() {
const createNode = this.container.get('CreateNode');
createNode(
{
paragraph: paragraphNode,
}
);
const createMark = this.container.get('CreateMark');
createMark(
{
comment: commentMark,
}
);
}
import { Service } from 'wax-prosemirror-core';
import bulletListNode from './schema/bulletListNode';
import BulletList from './BulletList';
class BulletListService extends Service {
name = 'BulletListService';
register() {
const CreateShortCut = this.container.get('CreateShortCut');
const createNode = this.container.get('CreateNode');
this.container.bind('BulletList').toDynamicValue(() => {
return new BulletList(this.config);
});
createNode(
{
bulletlist: bulletListNode,
},
{ toWaxSchema: true },
);
CreateShortCut({
'Shift-Ctrl-8': (state, dispatch) => {
this.container.get('BulletList').run(state, dispatch);
},
});
}
}
export default BulletListService;
Currently for adding a Rule through a Service is under Development.
You can add overlays like a tooltip for link or creating a new comment as in the demo
To do so, use createOverlay
boot() {
const createOverlay = this.container.get('CreateOverlay');
createOverlay(
LinkComponent,
{},
{
markType: 'link',
followCursor: false,
selection: false,
},
);
}
As first argument define your component. Pass optional props to your components. For the third argument, you can define, on what type of mark it will be triggered, if it will follow cursor placement , and setting markType to empty string
and setting selection to true
will trigger then the selection is >= 1.
By default the overlay will be positioned at the end of the annotation, or where the cursor is. Inside your React compontent you can define the new coords that will be drawn .
import React, { useLayoutEffect, useContext } from 'react';
import { WaxContext } from 'wax-prosemirror-core';
const MyComponent = ({
setPosition,
position,
}) => {
const { activeView, activeViewId } = useContext(WaxContext);
useLayoutEffect(() => {
const surface = activeView.dom.getBoundingClientRect();
const { selection } = activeView.state;
const { from, to } = selection;
const start = activeView.coordsAtPos(from);
const end = activeView.coordsAtPos(to);
const difference = end.top - start.top;
const left = surface.width + surface.x - 20;
const top = end.top - difference / 2 - 5;
//New component positioning.
setPosition({ ...position, left, top });
}, [position.left]);
}
The above example will place the overlay at the end of the editing surface for the comment creation, by using setPosition.
As we saw in the Layout example we used ComponentPlugin to register a new area. For example in Editoria’s demo footnotes are drawn into a new area underneath the main editor.
In our layout we have to register that area
import { ComponentPlugin } from 'wax-prosemirror-core';
const NotesArea = ComponentPlugin('notesArea');
We will then place it inside our layout, wherever we want the notes to appear.
<NotesArea />
Inside our Service , we will then render each note component into that area.
import NoteComponent from './NoteComponent';
class NoteService extends Service {
name = 'NoteService';
boot() {
const layout = this.container.get('Layout');
layout.addComponent('notesArea', NoteComponent);
}
}
boot() {
this.app.PmPlugins.add('myKey', CommentPlugin('myKey'));
}
You can then access the plugin inside your component
const { app, activeView } = useContext(WaxContext);
const myPlugin = app.PmPlugins.get('myKey');
const activeComment = myPlugin.getState(activeView.state).comment;
Use addPortal inside register method of your service.
register() {
const addPortal = this.container.get('AddPortal');
addPortal({
nodeView: MultipleChoiceNodeView,
component: QuestionComponent,
context: this.app,
});
}
nodeView is optional. Defining a nodeView class lets you overwrite methods like update
, ignoreMutation
etc.
import { AbstractNodeView } from 'wax-prosemirror-core';
export default class MultipleChoiceNodeView extends AbstractNodeView {
constructor(
node,
view,
getPos,
decorations,
createPortal,
Component,
context,
) {
super(node, view, getPos, decorations, createPortal, Component, context);
}
static name() {
return 'multiple_choice';
}
update() {
// do something
}
}
Through your editor global config you can pass configuration for each Service.
in your editor’s config you start the service and you can pass your configuration.
import { DefaultSchema } from 'wax-prosemirror-core';
import DummyService from './DummyService';
export default {
MenuService: [
{
templateArea: 'mainMenuToolBar',
toolGroups: [
'Base',
],
},
],
SchemaService: DefaultSchema,
DummyService: {value1: 'some value', value2: 'another one'}
services: [
new DummyService(),
],
};
Let’s assume we have the following Service.
import { Service } from "wax-prosemirror-core"
class DummyService extends Service {
name = 'DummyService'
boot() {}
register() {
console.log(this.config)
}
}
this.config
will hold the object passed from Wax main configuration file.
class InlineAnnotationsService extends Service {
dependencies = [
new CodeService(),
new StrongService(),
new EmphasisService(),
new SubscriptService(),
new SuperscriptService(),
new StrikeThroughService(),
new UnderlineService(),
new SmallCapsService()
];
}
Exclude certain tool from toolgroups through the config.
toolGroups: [{name: "Base", exclude: ['Undo']}, "Annotations", "Notes", "Lists", "Images", "Tables"]
Adding certain tools into more section toolGroups.
[ "Base", { name: "Annotations", more: ["Superscript", "Subscript", "SmallCaps"] } ]