Is there a way I can set every single new line to use <br>
tags and every double new lines to use <p>
tags as the default behavior for treating new lines?
You could bind enter to a command that inserts a hard break, except when directly after a hard break, in which case it remove the hard break and splits textblocks instead.
@marijn That’s exactly what I would like to accomplish. How would you go about removing a hard break through Prosemirror transaction?
This is what I have tried in the keymap command with no success:
if ($from.nodeBefore && $from.nodeBefore.type === hardBreak) {
dispatch(state.tr.delete($from.pos, $from.pos + $from.nodeBefore.nodeSize));
return false;
}
A command that has an effect and then returns false
seems dodgy—if you handle a key, you should probably return true.
The delete
call deletes from the cursor to the size of the node before the cursor after the cursor. Something like delete($from.pos - $from.nodeBefore.nodeSize, $from.pos)
seems more reasonable. Also, leave nodes like hard breaks always have node size 1, so you could simplify that.
Thanks @marijn! Your pointers helped to solve the issue. I got it working exactly the way OP intended.
Some important points to keep in mind:
- Every command that is chained to a key should have only one transaction dispatched
- After dispatching a transaction the command should return
true
- In order to make several changes with one transaction you need to chain the calls (
insert
,replaceWith
,delete
etc) -
delete
call deletes from the cursor to the size of the node before the cursor after the cursor
Here’s my simplified, but working code:
/**
* Helper function to remove hard break and insert paragraph instead.
* @param {Object} params
* @param {import('prosemirror-model').NodeType} params.hardBreak
* @param {import('prosemirror-model').NodeType} params.paragraph
*/
function removeHardBreakAndInsertParagraph({ hardBreak, paragraph }) {
/**
* @param {import('prosemirror-state').EditorState} state ProseMirror editor state.
* @param {Function} dispatch Editor's dispatch function.
*/
return (state, dispatch) => {
const { $from } = state.selection;
if (!$from.parent.isBlock) {
return false;
}
if ($from.parent.type !== paragraph) {
return false;
}
if ($from.nodeBefore && $from.nodeBefore.type !== hardBreak) {
return false;
}
if (dispatch) {
dispatch(
state.tr
.delete($from.pos - $from.nodeBefore.nodeSize, $from.pos)
.replaceSelectionWith(paragraph.create())
.scrollIntoView()
);
}
return true;
};
}
/**
* Helper function to insert hard break.
* @param {Object} params
* @param {import('prosemirror-model').NodeType} params.hardBreak
* @param {import('prosemirror-model').NodeType} params.paragraph
*/
function insertHardBreak({ hardBreak, paragraph }) {
/**
* @param {import('prosemirror-state').EditorState} state ProseMirror editor state.
* @param {Function} dispatch Editor's dispatch function.
*/
return (state, dispatch) => {
const { $from } = state.selection;
if (!$from.parent.isBlock) {
return false;
}
if ($from.parent.type !== paragraph) {
return false;
}
if (dispatch) {
dispatch(state.tr.replaceSelectionWith(hardBreak.create()).scrollIntoView());
}
return true;
};
}
/**
* Build keymap that binds keyboard combinations to editor actions.
* @param {import('prosemirror-model').Schema} schema Prosemirror schema.
* @return {Object}
*/
export function buildKeymap(schema) {
const keys = {};
const { hardBreak, paragraph } = schema.nodes;
keys.Enter = chainCommands(
removeHardBreakAndInsertParagraph({ hardBreak, paragraph })
insertHardBreak({ hardBreak, paragraph })
);
return keys;
}
There is some control for this on laptop browsers, but that same control does not work on mobile browsers:
Shift-CR seems to insert < br > instead of closing the < p > on laptop chrome browser.
But shift-CR on chrome mobile (and firefox mobile) does not insert the < br > and acts just like CR.
Control-CR on laptop also inserts < br >