Skip to content

Commit

Permalink
feat: set placeholder on heading on create
Browse files Browse the repository at this point in the history
  • Loading branch information
2wheeh committed Apr 20, 2024
1 parent 61c415d commit e66d355
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 35 deletions.
27 changes: 18 additions & 9 deletions ui/src/editor/plugins/placeholder/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { HeadingNode } from '@lexical/rich-text';
import { mergeRegister } from '@lexical/utils';
import { useEffect } from 'react';

import '@/editor/plugins/placeholder/styles.css';

import {
$removePlaceholderFromParagraphNodes,
$updatePlaceholder,
$setPlaceholderOnCreate,
$setPlaceholderOnSelectedParagraphNode,
} from '@/lib/utils/editor/placeholder';

export function PlaceholderPlugin() {
const [editor] = useLexicalComposerContext();

useEffect(() => {
return editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
// Remove placeholder from all paragraph nodes
$removePlaceholderFromParagraphNodes(editor);
// Update the placeholder for the current selection
$updatePlaceholder(editor);
});
});
return mergeRegister(
editor.registerMutationListener(HeadingNode, (nodes) => {
// Set placeholder to heading nodes on create
$setPlaceholderOnCreate(nodes, editor);
}),
editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
// Remove placeholder from all paragraph nodes
$removePlaceholderFromParagraphNodes(editor);
// Set placeholder for the currently selected paragraph node
$setPlaceholderOnSelectedParagraphNode(editor);
});
})
);
}, [editor]);

return null;
Expand Down
58 changes: 32 additions & 26 deletions ui/src/lib/utils/editor/placeholder.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { $isHeadingNode } from '@lexical/rich-text';
import {
$getRoot,
$getSelection,
$isParagraphNode,
$isRangeSelection,
ParagraphNode,
} from 'lexical';
import type { LexicalEditor, LexicalNode } from 'lexical';
import type { LexicalEditor, LexicalNode, NodeKey, NodeMutation } from 'lexical';

import {
DATA_PLACEHOLDER,
Expand All @@ -20,53 +19,60 @@ export function $removePlaceholderFromParagraphNodes(editor: LexicalEditor) {
return $removeClassesFromNodesOfType(editor, ParagraphNode, PLACEHOLDER_CLASS_NAME);
}

function $getPlaceholderText(node: LexicalNode) {
if ($isHeadingNode(node)) {
const tag = node.getTag();
function $getPlaceholderText(element: HTMLElement) {
const tag = element.tagName;

if (tag.startsWith('H')) {
const level = tag.charAt(tag.length - 1);
return `${HEADING_PLACEHOLDER}${level}`;
}

if ($isParagraphNode(node)) {
if (tag === 'P') {
return PARAGRAPH_PLACEHOLDER;
}

return null;
}

// If the selected node is the first child of the root node and it's not a heading,
// we use the default placeholder from the <RichTextPlugin />
function $shouldUseDefaultPlaceholder(node: LexicalNode) {
return node === $getRoot().getFirstChild() && !$isHeadingNode(node);
}
function $setPlaceholder(nodeKey: string, editor: LexicalEditor) {
const element = editor.getElementByKey(nodeKey);

function $applyPlaceholderToNode(node: LexicalNode, placeholder: string, editor: LexicalEditor) {
const element = editor.getElementByKey(node.getKey());
if (element === null) {
return;
}

const placeholder = $getPlaceholderText(element);

if (placeholder === null) {
return;
}

element.classList.add(PLACEHOLDER_CLASS_NAME);
element.setAttribute(DATA_PLACEHOLDER, placeholder);
}

export function $updatePlaceholder(editor: LexicalEditor) {
export function $setPlaceholderOnCreate(nodes: Map<NodeKey, NodeMutation>, editor: LexicalEditor) {
for (const [key, mutation] of nodes) {
if (mutation === 'created') {
$setPlaceholder(key, editor);
}
}
}

// Set placeholder when the selected node is paragraph node and not the first child of the root node,
// Otherwise we use the default placeholder from the <RichTextPlugin />
function $shouldSetPlaceholder(node: LexicalNode) {
return $isParagraphNode(node) && node !== $getRoot().getFirstChild();
}

export function $setPlaceholderOnSelectedParagraphNode(editor: LexicalEditor) {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return;
}

const targetNode = selection.anchor.getNode();
if ($shouldUseDefaultPlaceholder(targetNode)) {
return;
}

// Get the appropriate placeholder for the selected node
const placeholder = $getPlaceholderText(targetNode);

// If there's no placeholder for this node type, we don't do anything
if (placeholder === null) {
return;
if ($shouldSetPlaceholder(targetNode)) {
$setPlaceholder(targetNode.getKey(), editor);
}

$applyPlaceholderToNode(targetNode, placeholder, editor);
}

0 comments on commit e66d355

Please sign in to comment.