DEV Community

Santosh Viswanatham
Santosh Viswanatham

Posted on • Originally published at isantoshv.Medium on

Notion like Placeholders for every line — Slatejs

Notion like Placeholders for every line — Slatejs

Notepad
Photo by Diana Polekhina on Unsplash

I have recently worked on building a module that includes a google docs kind of functionality. I was exploring several JavaScript frameworks around building editors, and I finally choosed Slatejs

One feature of my editor was to have a placeholder for every line similar to Notion. By default, Slatejs provides a placeholder but it will be rendered if the document only contains a single empty block.

However, Slate.js doesn’t have the feature that I needed. I was able to figure out a solution with the help of the amazing Slate.js community members and thought to share this with you.

The solution I implemented was to check if a node is empty and then display a dummy text using CSS tricks.

.selected-empty-element {
  position: relative;
}

.selected-empty-element:after {
  content: " Use '/' to create question";
  color: #aaa;
  position: absolute;
  top: 0;
}
Enter fullscreen mode Exit fullscreen mode

In the render element method, I will add this class selected-empty-element if the node is empty.

const Element = (props): JSX.Element => {
  const { children } = props;
  const isEmpty =
    children.props.node.children[0].text === “” &&
    children.props.node.children.length === 1;

  return (
    <p {…props} className={isEmpty ? “selected-empty-element” : “”}>
      {children}
    </p>
  );
};
Enter fullscreen mode Exit fullscreen mode

But this gives out a weird user experience when you have multiple empty rows. All empty lines will show the placeholders.

Multiple empty rows in editor

So one solution I could think of was to check if that particular line has focus. I added a check using useSelected, and it looked exactly like the solution I needed.

const selected = useSelected();
return (
  <p {...props} 
    className={selected && isEmpty ? "selected-empty-element" : ""}
  >
    {children}
  </p>
);
Enter fullscreen mode Exit fullscreen mode

placeholder only on selection

However, there was one problem that I found later on. If I select the whole document, then the useSelected is true for all nodes and I could see the placeholder for all nodes.

the whole document is selected

Now I had to add another check to see if my selection is empty or not. I used the available library methods to see if the Range is collapsed or not using

const editor = useSlate();
const selection = editor.selection;
let isSelectionCollapsed = true;
if (selection !== null)
 isSelectionCollapsed = Range.isCollapsed(editor.selection);
Enter fullscreen mode Exit fullscreen mode

So now the final Element code is

const Element = (props): JSX.Element => {
  const { children } = props;
  const selected = useSelected();
  const editor = useSlate();
  const selection = editor.selection;
  let isSelectionCollapsed = true;
  if (selection !== null)
    isSelectionCollapsed = Range.isCollapsed(editor.selection);

  const isEmpty =
    children.props.node.children[0].text === “” &&
    children.props.node.children.length === 1;

  return (
    <p {…props}
      className={ selected && isSelectionCollapsed && isEmpty
        ? “selected-empty-element” : “”
      }
    >
      {children}
    </p>
  );
};
Enter fullscreen mode Exit fullscreen mode

This was the final solution that I needed. You can find the complete editor code in this sandbox here -

I hope this was helpful. Share with me some of the interesting features you built using Slatejs.

Discussion (0)