Skip to content

Commit

Permalink
feat($transformTextToMentionNodes): add utility function to transform…
Browse files Browse the repository at this point in the history
… text nodes to mention nodes (#609)
  • Loading branch information
sodenn committed Aug 23, 2024
1 parent 3259515 commit e71a286
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/tidy-mugs-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"lexical-beautiful-mentions": patch
---

feat($transformTextToMentionNodes): add utility function to transform text nodes to mention nodes
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,37 @@ The `punctuation` property allows you to specify the punctuation characters that
#### PreTriggerChars

The `preTriggerChars` property allows you to specify a set of characters that can appear directly before the trigger character. By default, only the open bracket is allowed (e.g. `(@Alice)`).

### Utility Functions for Mention Conversion

The plugin provides two utility functions to help with converting text to mention nodes:

#### `$convertToMentionNodes`

This function converts a string or a text node into a list of mention and text nodes.

Usage example:

```typescript
import { $convertToMentionNodes } from 'lexical-beautiful-mentions';

const text = "Hello @Alice and #world";
const nodes = $convertToMentionNodes(text, ['@', '#']);
// nodes will be an array of TextNodes and BeautifulMentionNodes
```

#### `$transformTextToMentionNodes`

This function transforms text nodes in the editor that contain mention strings into mention nodes.

Usage example:

```typescript
import { $transformTextToMentionNodes } from 'lexical-beautiful-mentions';

editor.update(() => {
$transformTextToMentionNodes(['@', '#']);
});
```

**Note:** Both functions only work for mentions without spaces. Ensure spaces are disabled via the `allowSpaces` prop.
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
import { CodeNode } from "@lexical/code";
import { LinkNode } from "@lexical/link";
import { ListItemNode, ListNode } from "@lexical/list";
import {
$convertFromMarkdownString,
$convertToMarkdownString,
TRANSFORMERS,
} from "@lexical/markdown";
import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
import { $nodesOfType, createEditor, ParagraphNode, TextNode } from "lexical";
import { describe, expect, it } from "vitest";
import { convertToMentionEntries } from "./mention-converter";
import {
$transformTextToMentionNodes,
convertToMentionEntries,
} from "./mention-converter";
import { DEFAULT_PUNCTUATION } from "./mention-utils";
import { BeautifulMentionNode } from "./MentionNode";
import { ZeroWidthNode } from "./ZeroWidthNode";

describe("mention-utils", () => {
describe("mention-converter", () => {
it("should find mention entries in text", () => {
const triggers = ["@", "due:", "#"];
const text = "Hey @john, the task is #urgent and due:tomorrow";
Expand Down Expand Up @@ -121,4 +137,49 @@ describe("mention-utils", () => {
expect(entries[2].type).toBe("text");
expect(entries[2].value).toBe(")");
});

it("should transform mention string to mention nodes", async () => {
const editor = createEditor({
nodes: [
BeautifulMentionNode,
ZeroWidthNode,
HorizontalRuleNode,
CodeNode,
HeadingNode,
LinkNode,
ListNode,
ListItemNode,
QuoteNode,
],
onError(err) {
throw err;
},
});
editor.update(() => {
$convertFromMarkdownString(
"Hey @catherine, the **task** is #urgent",
TRANSFORMERS,
);
$transformTextToMentionNodes(["@", "#"]);

const paragraph = $nodesOfType(ParagraphNode);
const nodes = paragraph[0].getChildren();

expect(nodes[0]).toBeInstanceOf(TextNode);
expect(nodes[0].getTextContent()).toBe("Hey ");
expect(nodes[1]).toBeInstanceOf(BeautifulMentionNode);
expect(nodes[1].getTextContent()).toBe("@catherine");
expect(nodes[2]).toBeInstanceOf(TextNode);
expect(nodes[2].getTextContent()).toBe(", the ");
expect(nodes[3]).toBeInstanceOf(TextNode);
expect(nodes[3].getTextContent()).toBe("task");
expect(nodes[4]).toBeInstanceOf(TextNode);
expect(nodes[4].getTextContent()).toBe(" is ");
expect(nodes[5]).toBeInstanceOf(BeautifulMentionNode);
expect(nodes[5].getTextContent()).toBe("#urgent");

const markdown = $convertToMarkdownString(TRANSFORMERS);
expect(markdown).toBe("Hey @catherine, the **task** is #urgent");
});
});
});
59 changes: 52 additions & 7 deletions plugin/src/mention-converter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { $createTextNode, LexicalNode } from "lexical";
import {
$createTextNode,
$getRoot,
$isElementNode,
$isTextNode,
LexicalNode,
TextNode,
} from "lexical";
import { $createBeautifulMentionNode } from "./MentionNode";
import {
DEFAULT_PUNCTUATION,
Expand Down Expand Up @@ -90,24 +97,62 @@ export function convertToMentionEntries(
}

/**
* Utility function that takes a string and converts it to a list of mention
* and text nodes.<br>
* 🚨 Only works for mentions without spaces. Make sure to disable spaces via
* the `allowSpaces` prop.
* Utility function that takes a string or a text nodes and converts it to a
* list of mention and text nodes.
*
* 🚨 Only works for mentions without spaces. Ensure spaces are disabled
* via the `allowSpaces` prop.
*/
export function $convertToMentionNodes(
text: string,
textOrNode: string | TextNode,
triggers: string[],
punctuation = DEFAULT_PUNCTUATION,
) {
const text =
typeof textOrNode === "string" ? textOrNode : textOrNode.getTextContent();
const entries = convertToMentionEntries(text, triggers, punctuation);
const nodes: LexicalNode[] = [];
for (const entry of entries) {
if (entry.type === "text") {
nodes.push($createTextNode(entry.value));
const textNode = $createTextNode(entry.value);
if (typeof textOrNode !== "string") {
textNode.setFormat(textOrNode.getFormat());
}
nodes.push(textNode);
} else {
nodes.push($createBeautifulMentionNode(entry.trigger, entry.value));
}
}
return nodes;
}

/**
* Transforms text nodes containing mention strings into mention nodes.
*
* 🚨 Only works for mentions without spaces. Ensure spaces are disabled
* via the `allowSpaces` prop.
*/
export function $transformTextToMentionNodes(
triggers: string[],
punctuation = DEFAULT_PUNCTUATION,
) {
const root = $getRoot();
const nodes = root.getChildren();

const traverseNodes = (nodes: LexicalNode[]) => {
for (const node of nodes) {
if ($isTextNode(node)) {
const newNodes = $convertToMentionNodes(node, triggers, punctuation);
if (newNodes.length > 1) {
const parent = node.getParent();
const index = node.getIndexWithinParent();
parent?.splice(index, 1, newNodes);
}
} else if ($isElementNode(node)) {
traverseNodes(node.getChildren());
}
}
};

traverseNodes(nodes);
}

0 comments on commit e71a286

Please sign in to comment.