Skip to content

Instantly share code, notes, and snippets.

@joshdover
Last active July 30, 2021 12:58
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshdover/7c5e61ed68cc5552dc8a25463e357960 to your computer and use it in GitHub Desktop.
Save joshdover/7c5e61ed68cc5552dc8a25463e357960 to your computer and use it in GitHub Desktop.
Example of how to add metadata to blocks for handling text alignment correctly
import { Modifier, EditorState, RichUtils } from 'draft-js';
import getCurrentlySelectedBlock from './getCurrentlySelectedBlock';
export const ALIGNMENTS = {
CENTER: 'center',
JUSTIFY: 'justify',
LEFT: 'left',
RIGHT: 'right'
};
export const ALIGNMENT_DATA_KEY = 'textAlignment';
const ExtendedRichUtils = Object.assign({}, RichUtils, {
// Largely copied from RichUtils' `toggleBlockType`
toggleAlignment(editorState, alignment) {
const { content, currentBlock, hasAtomicBlock, target } = getCurrentlySelectedBlock(editorState);
if (hasAtomicBlock) {
return editorState;
}
const blockData = currentBlock.getData();
const alignmentToSet = blockData && blockData.get(ALIGNMENT_DATA_KEY) === alignment ?
undefined :
alignment;
return EditorState.push(
editorState,
Modifier.mergeBlockData(content, target, {
[ALIGNMENT_DATA_KEY]: alignmentToSet
}),
'change-block-data'
);
},
/*
* An extension of the default split block functionality, originally pulled from
* https://github.com/facebook/draft-js/blob/master/src/component/handlers/edit/commands/keyCommandInsertNewline.js
*
* This version ensures that the text alignment is copied from the previously selected block.
*/
splitBlock(editorState) {
// Original split logic
const contentState = Modifier.splitBlock(
editorState.getCurrentContent(),
editorState.getSelection()
);
const splitState = EditorState.push(editorState, contentState, 'split-block');
// Assign alignment if previous block has alignment. Note that `currentBlock` is the block that was selected
// before the split.
const { currentBlock } = getCurrentlySelectedBlock(editorState);
const alignment = currentBlock.getData().get(ALIGNMENT_DATA_KEY);
if (alignment) {
return ExtendedRichUtils.toggleAlignment(splitState, alignment);
} else {
return splitState;
}
}
});
export default ExtendedRichUtils;
const getCurrentlySelectedBlock = (editorState) => {
const selection = editorState.getSelection();
const startKey = selection.getStartKey();
let endKey = selection.getEndKey();
const content = editorState.getCurrentContent();
let target = selection;
// Triple-click can lead to a selection that includes offset 0 of the
// following block. The `SelectionState` for this case is accurate, but
// we should avoid toggling block type for the trailing block because it
// is a confusing interaction.
if (startKey !== endKey && selection.getEndOffset() === 0) {
const blockBefore = content.getBlockBefore(endKey);
if (!blockBefore) {
throw new Error('Got unexpected null or undefined');
}
endKey = blockBefore.getKey();
target = target.merge({
anchorKey: startKey,
anchorOffset: selection.getStartOffset(),
focusKey: endKey,
focusOffset: blockBefore.getLength(),
isBackward: false
});
}
const hasAtomicBlock = content.getBlockMap()
.skipWhile((_, k) => k !== startKey)
.takeWhile((_, k) => k !== endKey)
.some(v => v.getType() === 'atomic');
const currentBlock = content.getBlockForKey(startKey);
return {
content,
currentBlock,
hasAtomicBlock,
target
};
};
export default getCurrentlySelectedBlock;
@creativenode
Copy link

I've implemented this, without errors, but no formatting is being added to the editor. What am I missing? Do I need to add a styles/CSS for this to work?

@sinslav
Copy link

sinslav commented Dec 14, 2016

@creativenode - thats because you need to provide your blockStyleFn func:

  blockStyleFn = (contentBlock) => {
    const textAlignStyle = contentBlock.getData().get(ALIGNMENT_DATA_KEY);
    switch (textAlignStyle) {
      case 'RIGHT':
        return `align-right`;
      case 'CENTER':
        return `align-center`;
      case 'LEFT':
        return `align-left`;
      case 'JUSTIFY':
        return `align-justify`;
    }
  }

and then provide css for .align-* classes

@ace139
Copy link

ace139 commented Apr 21, 2017

Can you please provide a working example in jsfiddle/codepen or similar online based js renderer. I am having problems putting the blocks together.

@blackChef
Copy link

@joshdover Can you explain how can I use splitBlock method ?

@MikeLP
Copy link

MikeLP commented Aug 28, 2017

Yeah, please explain.

@jaredtmartin
Copy link

jsfiddle/codepen would be great for this!

@michorvath
Copy link

michorvath commented Jul 30, 2021

I tried to set up a codepen to share a fully working example but I used Material UI and it was a nightmare to try and get that running in codepen. So instead I will just briefly explain that to use the splitBlock method you want to add a prop (handleReturn) to the Editor component that comes from Draft, like so:

import React, { useState } from 'react';
import { Editor, EditorState } from 'draft-js';
import ExtendedRichUtils from './utils/ExtendedRichUtils.js';

function RichTextEditor() {
  const [editorState, setEditorState] = useState(EditorState.createEmpty());
  
  return (
    <Editor
      editorState={editorState}
      onChange={setEditorState}
      handleReturn={() => {
        setEditorState(ExtendedRichUtils.splitBlock(editorState));
        return 'handled';
      }}
    }
  );
/>

The handleReturn method is briefly mentioned here. Returning the string "handled" prevents the default behavior and lets us supplement our own.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment