Changing the way line breaks are handled

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?

1 Like

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:

  1. Every command that is chained to a key should have only one transaction dispatched
  2. After dispatching a transaction the command should return true
  3. In order to make several changes with one transaction you need to chain the calls (insert, replaceWith, delete etc)
  4. 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;
}
1 Like