Configuration for registering block level rich text element types.
In rich text documents, elements such as paragraphs, images, links, etc. are created using RTElement
objects. Before such elements can be created, the element type needs to be registered using the RTElements.register
method.
The configuration object for block level elements. See below the table for additioal details.
Property | Type | Description |
---|---|---|
level* | 'block' | The level at which the element is rendered, always 'block'. |
type* | string | The element type, such as 'paragraph'. |
component* | React.ComponentType | The component used to render the element. |
initializeDataFromFragment | function | Called when creating a new element of this type from a rich text fragment. Should return the initial state of any custom data used in the element. Can be omitted if the element simply uses the fragment as its children or not at all. |
initializeDataFromFile | function | Called when creating a new element of this type from a file. Should return the initial state of any custom data used in the element. Can be omitted if the element does not support creation from files. |
convertData | function | Called when an existing rich text element of a different type is converted into this type. Omit if the element does not need to perform additional logic during element conversions. |
toPlainText | function | Function which returns a plain text version of the element's content. |
void | boolean | Determines whether the element is editable. |
allowNesting | boolean | Whether other block level elements can indented below this one. Indented block IDs will be set as the element's |
returnBehaviour | | 'break-out' | Determines the bahviour when the Return key is pressed with focus within an element of this type. See below for details. |
dataTypes | string[] | The data types from which this type of element can be created (e.g. |
fileTypes | string[] | The file types from which this type of element can be created (e.g. |
multiFile | boolean | When |
domains | (string | UrlValidator)[] | The domains from which this type of element can be created. |
shortcuts | string[] | An array of markdown style shorcuts which trigger the creation of an element of this type |
hotkeys | BlockHotkey[] | The hotkeys related to this element. |
htmlDeserializers | HtmlDeserializerMap | Deserializer functions used to deserialize pasted HTML into this type of element. |
Rich text element components are just simple React components. However, there are two required props which need to be applied correctly in order for the element to function:
attributes
: must be spread onto the root HTML element
children
: must be rendered at the location at which the text will be edited
Below is an example of a non-void (in which text can be edited) paragraph element. It will render a paragraph inside which the text is editable.
const ParagraphElement: React.FC<RTBlockElementProps> = ({
attributes,
children,
}) => <p {...attributes}>{children}</p>;
Below is an example of a void (in which there is no text to edit) image element. Note that children
must be rendered even though the image doesn't actaully have editable children (it renders an empty span element required for editor functionality).
interface ImageElementProps extends RTBlockElementProps {
/**
* The image src.
*/
src: string;
}
const ImageElement: React.FC<ImageElementProps> = ({
attributes,
children,
element,
}) => (
<div {...attributes}>
<img src={element.src} />
{children}
</div>
);
The props passed to block element components.
Property | Type | Description |
---|---|---|
attributes* | Record<string, string> | HTML attributes required for the editor to function. Should be spread onto the root HTML element of the component. |
children* | React Children | Renders the editable rich text content. Must be rendered even if the element is void and does not support rich text content (it renders an empty span element required for editor functionality). |
element* | RTElement | The rich text element being rendered. |
Called when creating a new rich text element of this type from a rich text fragment. Should return an object containing the initial state of the custom data used by the element. Can be omitted if the element simply uses the fragment as its children or not at all.
initializeDataFromFragment<
TCustomData extends {}
>(fragment?: RTFragment): TCustomData
Argument | Type | Description |
---|---|---|
core* | Core | A MindDrop core instance. |
fragment* | RTFragment | A rich text fragment. |
Imagine an "equation" element which renders mathematical expression, the initializeDataFromFragment
method could be used to set the fragment's plain text value as the expression value:
createFromFragment: (core, fragment) => ({ expression: RTElements.toPlainText(fragment) });
Called when creating a new rich text element of this type from one or more files. Should return an object containing the initial state of the custom data used by the element. Omit if the element does not support being created from files.
When creating elements from files, a file reference must be created for each file using Files.create
.
createFromFile<TCustomData extends {}>(file?: File | File[]): TCustomData
Argument | Type | Description |
---|---|---|
core* | Core | A MindDrop core instance. |
file* | File | File[] | The file or files from which to initialize the data. Always a single file if multiFile is false. Always an array of files if multiFile it true. |
Imagine a simple "image" element which stores the displayed image's file reference ID using a file
property.
initializeDataFromFile: (core, file) => {
// Save the file
const fileReference = Files.create(core, file);
return {
file: fileReference.id,
}
}
Called when an existing rich text element of a different type is converted into this type (e.g. converting a 'paragraph' element into an 'equation' element). Omit if the element does not need to perform additional logic during element conversions.
convert<TCustomData extends {}>(element: RTBlockElement): TCustomData
Argument | Type | Description |
---|---|---|
core* | Core | A MindDrop core instance. |
element* | RTBlockElement | The rich text element being converted, always a block level element. |
The conversion of an element from one type to another follows these steps:
The default data for the new element type is generated using the element's createFromFragment
method (if defined).
The conversion data is generated using the new element's convertData
method (if defined).
The element's custom data fields (if present) are removed.
If converting to a void element type, children
is removed. Conversly, if converting from a void element type to a non-void element type, children
(consisting of a RTFragment
containing the original element's plain text value) is added.
The default data (generated in step 1) is merged into the element.
The conversion data (generated in step 2) is merged into the element.
The element's type
is changed to the new type.
For an 'equation' element which stores the equation expression under a field called expression
, conversion could be done by setting the converted element's plain text value as the expression
.
convertData: (core, element) => ({
expression: RTElements.toPlainText(element),
});
A function which returns a plain text version of the element's content. Only needed for void elements which use text based data (e.g. a 'equation' element in which the equation expression is edited in a popup field).
Providing a toPlainText
method is strongly recommended for void elements which contain any text based data. The method has several uses:
Search: MindDrop's search indexing uses the plain text version of rich text documents. Providing a toPlainText
method makes void elements searchable.
Copy/Paste: When copying text from the rich text editor, a plain text version of the text is added to the clipboard data to support pasting outside of MindDrop.
Unregister: When a void rich text element type is unregistered (e.g. because the extension was uninstalled), the elements of that type are converted into the default element type using the plain text value as the content.
Conversion: Some element types use the plain text content to initialize custom data fields during conversion (see convertData above).
toPlainText(element: RTBlockElement): string
Argument | Type | Description |
---|---|---|
core* | Core | A MindDrop core instance. |
element* | RTBlockElement | The element to convert to plain text. |
Void elements are elements which do not involve text (e.g. an 'image' element), or elements in wich the text is not directly a part of the editor (e.g. an 'equation' element in which the equation expression is edited in a popup field).
Non-void elements must have a children
field set to a RTFragment
.
What happens when the Return key is pressed at the end of a block element:
'break-out'
inserts a new element of the default type below, this is the default behaviour.
'same-type'
inserts a new element of the same type as this one below.
'line-break'
inserts a soft line break (\n
) into the current element.
(element: RTBlockElement) => Partial<TypeData>
inserts a new element of the same type as this one below and sets the returned data on the element. Receives the new element as its only parameter.
If the Return key is pressed within the element's text, the element is split in two, creating a second element of the same type below, unless returnBehaviour is set to 'line-break'
in which case a line break is insterted.
The data types from which this kind of element can be created (e.g. 'text/plain'
). Used to decide which type of element to create when data is inserted into the editor (e.g. from a paste event).
When a data insert contains a matching data type, the createFromFragment
method will be called with the inserted data. If there are multiple registered rich text element types that support the same data type, only a single element will be created (the element type that was registered first).
Omit if this element type does not support being created from data.
Note that 'text/html'
data is ommited as creating elements from HTML is handled separately using htmlDeserializers
.
The file types from which this kind of element can be created (e.g. 'image/png'
). Used to decide which type of element to create when data containg files is inserted into the editor (e.g. from a paste event).
When a data insert contains a matching file type, the createFromFragment
method will be called with the inserted file(s). If there are multiple registered rich text element types that support the same file type, only a single element will be created (the element type that was registered first).
Omit if this element type does not support being created from files.
Determnies the behaviour when creating elements of this type from files.
When true
, indicates that this element type supports multiple files at once, resulting in the createFromFragment
method being called once with all supported files included in the data parameter.
When false
, indicates that this element type only supports a single file per element. In this case, the createFromFragment
method will called for each inserted file, with a signle file being included in the data parameter.
The domains from which this type of element can be created. Used to decide which type of element to create when a URL is inserted (e.g. pasted) into the editor.
The domains
value can contain one or more of the following:
A domain name, such as 'minddrop.app'
. This will match against any page on the 'minddrop.app' domain as well as 'www.minddrop.app', but not any other subdomains (e.g. 'docs.minddrop.app'). An asterisk can be used as a wildcard to match againt any subdomain or top level domain (e.g. '*.minddrop.com'
and 'minddrop.*'
).
An asterisk ('*'
) to match against all domains.
A UrlValidator
function, which receives the URL as its only parameter and returns a boolean indicating whether an element should be created from the URL.
If there are multiple registered rich text element types that support the same domains, only a single element will be created (the element type that was registered first).
A function which validates a URL, return a boolean indicating whether the URL matches requirements.
(url: string) => boolean;
Argument | Type | Description |
---|---|---|
url* | string | The URL to validate. |
An array of markdown style shorcuts which trigger the creation of an element of this type (e.g. '* '
to create a list item when typing an asterisk followed by a space).
The shortcut is only triggered if it was typed at the start of the currently focused element. When triggered, calls the convert
method on the focused element. Therefor, shortcuts are only supported in elements which allow conversion.
The shortcut text is automatically removed when the shortcut is triggered.
The hotkeys related to this element. Block element hotkeys can be used to convert elements of another type to this type, or modify the data on existing elements of this type if they are currently focused.
Hotkeys are configured using BlockHotkey
objects with a keys
property, which is the list of keys which when pressed together trigger the action, and an action
property. The action property can be one of two types:
'convert'
: setting the action to 'convert' will cause the currently selected/focused block(s) to be converted to this type when triggered.
BlockHotkeyAction
: setting the action a function will call the function when triggered. It is only triggered if the currently selected/focused block(s) are of this type. The function should return updated custom data for the element type. If multiple elements are selected, the function is called once per element of this type.
Modifier keys:
Use 'Ctrl'
for the Control key (maps to Command key on Mac)
Use 'Alt'
from the Alt key (maps to the Option key on Mac).
Use 'Shift'
for the Shift key.
For other keys, simply use the character itslef (e.g. 'A'
, '1'
, '#'
). These are case insensitive.
For instance, the hotkey ['Ctrl', 'Shift', '1']
would trigger when the Control
+ Shift
+ 1
(Command
+ Shift
+ 1
on Mac) keys are pressed in unison.
The following hotkey toggles a focused to-do element's 'done' state using Ctrl + Enter
.
{
keys: ['Ctrl', 'Enter'],
action: (element) => ({ done: !element.done }),
}
Property | Type | Description |
---|---|---|
keys* | string[] | List of keys used to trigger the action. |
action | 'convert' | BlockHotkeyAction | The action triggered by the hotkey. |
Callback fired when an inline hotkey is triggered. Should return the custom type data to set onto the element.
<TTypeData extends RTElementTypeData>(
core: Core,
element: RTBlockElement,
): TTypeData;
Argument | Type | Description |
---|---|---|
core* | Core | A MindDrop core instance. |
element* | RTBlockElement | The target block element on which the hotkey was triggered. |
HTML deserializers are called when HTML data is inserted into the editor, usually as a result of text being copy pasted into the editor from a web page. They should return an object (or array of objects) containing the element type
as well as any data used by the element, or null
if an element should not be created.
HTML deserializers are configured using a { [node name]: HtmlDeserializer }
map, where [node name]
corresponds to an HTML element node name (in all caps), such a SPAN, A, IMG, etc. Use an asterisk (*) as the node name in order to match against all HTML element types. If multiple registered rich text element types configure a deserializer for the same HTML element, the deserializers will be run one after the other (in the order they were registered) until a non null
value is returned. HTML deserializers bubble up, meaning that the most deeply nested elements are deserialized first, followed by their parent element.
For non-void elements, children
will be automatically added to the resulting element.
The example below demonstrates an HTML deserializer which turns ordered list item elements into an 'ordered-list' element. The deserialization happens in two stages. First, the individual <li>
elements are deserialized into { type: 'ordered-list' }
elements. Then, when the deserializer moves up to the parent <ol>
element, the list item number is added to the previously deserialized list items based on their index.
deserializers = {
LI: (element, parent) => {
// Only create an 'ordered-list' element if
// the parent is an `<ol>` element
if (parent && parent.nodeName === 'OL') {
return { type: 'ordered-list' };
}
// Don't create an 'ordered-list' element if
// the parent is not an `<ol>` element
return null;
},
OL: (element, parent, children) =>
// The `children` in this case are the elements
// deserialized by the LI deserializer above.
// Here, we add the number to the list items.
children.map((child, index) => ({
...child,
number: index + 1,
})),
};
Callback used to deserialize an HTML element.
deserializeHtmlElement(
element: HTMLElement,
parent: HTMLElement | null,
children: RTFragment | RTBlockElement[] | null,
): CreateRTBlockElementData | CreateRTBlockElementData[] | null
Argument | Type | Description |
---|---|---|
element* | HTMLElement | The HTML element to deserialize. |
parent* | HTMLElement | null | The element's parent element, or null if it has no parent. |
children* | | RTFragment | The element's deserialized child elements or null if the element has no children. |