Giter VIP home page Giter VIP logo

lexical-beautiful-mentions's Introduction

lexical-beautiful-mentions

CI status CodeQL status

Demo

A mentions plugin for the lexical editor. lexical is an extendable text editor for the web build by Meta. While the lexical playground offers a basic mentions plugin for demo purposes, this plugin is more advanced and offers more features.

  • Customizable triggers: Use characters, words or regular expressions as triggers for mentions.
  • Multiple triggers: You can define multiple triggers (e.g. @ and #).
  • Editing mentions outside the editor: Programmatically insert, delete, or rename mentions via the useBeautifulMentions hook.
  • Automatic spacing: The plugin automatically adds spaces around the mentions, which makes it easier for the user to continue typing.
  • Adding new mentions: You can allow users to create new mentions that are not in the suggestion list.
  • Async query function: You can use an async query function to provide mentions for the suggestion list.
  • Additional metadata: You can add additional metadata to the mention items, which will be included in the mention nodes when serializing the editor content.
  • Customizable mention style: You can change the look of the mentions via the editor theme to match the style of your application.
  • Custom menu and menu item: You can customize the look and behavior of the menu that displays the mention suggestions.
  • Custom mention component: You can replace the default mention component with a custom component of your choice.

Installation

To install the plugin, run the following command:

// with npm
npm install lexical-beautiful-mentions

// with yarn
yarn add lexical-beautiful-mentions

You also need to install the lexical and @lexical/react, which is a peer dependency of this plugin.

Usage

Import the BeautifulMentionsPlugin plugin:

import { BeautifulMentionsPlugin, BeautifulMentionNode } from "lexical-beautiful-mentions";

Add the plugin to the lexical editor:

import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";

const mentionItems = {
  "@": ["Anton", "Boris", "Catherine", "Dmitri", "Elena", "Felix", "Gina"],
  "#": ["Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape"],
  "due:": ["Today", "Tomorrow", "01-01-2023"],
};

const editorConfig = {
  // ...
  nodes: [BeautifulMentionNode] // ๐Ÿ‘ˆ register the mention node
};

return (
  <LexicalComposer initialConfig={editorConfig}>
    <RichTextPlugin // ๐Ÿ‘ˆ use the RichTextPlugin to get clipboard support for mentions
      contentEditable={/* ... */}
      placeholder={/* ... */}
      ErrorBoundary={/* ... */}
    />
    <BeautifulMentionsPlugin // ๐Ÿ‘ˆ add the mentions plugin
      items={mentionItems}
    />
    {/** ... */}
  </LexicalComposer>
);

Customize mention style


import { BeautifulMentionsTheme } from "lexical-beautiful-mentions";
// ...
const beautifulMentionsTheme: BeautifulMentionsTheme = {
  // ๐Ÿ‘‡ use the trigger name as the key
  "@": "px-1 mx-px ...",
  // ๐Ÿ‘‡ add the "Focused" suffix to style the focused mention
  "@Focused": "outline-none shadow-md ...",
  // ๐Ÿ‘‡ use a class configuration object for advanced styling
  "due:": {
    trigger: "text-blue-400 ...",
    value: "text-orange-400 ...",
    container: "px-1 mx-px ...",
    containerFocused: "outline-none shadow-md ...",
  },
}
// ๐Ÿ‘‡ add the mention theme to the editor theme
const editorConfig = {
  // ...
  theme: {
    // ...
    beautifulMentions: beautifulMentionsTheme,
  },
};

// ...

return (
  <LexicalComposer initialConfig={editorConfig}>
    {/** ... */}
  </LexicalComposer>
);

Custom mention node and component

If applying styles via the theme is not enough, you can replace the BeautifulMentionNode by using the lexical Node Overrides API. This allows you to change the default behavior of the mention node:

export class CustomMentionsNode extends BeautifulMentionNode {
  static getType() {
    return "custom-beautifulMention";
  }
  static clone(node: CustomBeautifulMentionNode) {
    // TODO: implement
  }
  static importJSON(serializedNode: SerializedBeautifulMentionNode) {
    // TODO: implement
  }
  exportJSON(): SerializedBeautifulMentionNode {
    // TODO: implement
  }
  component(): ElementType<BeautifulMentionComponentProps> | null {
    // the component that renders the mention in the editor
    // return null to use the default component
    // ๐Ÿ’ก if you only want to replace the component use the `createBeautifulMentionNode` helper method. See below for more details ๐Ÿ‘‡
  }
  decorate(editor: LexicalEditor, config: EditorConfig): React.JSX.Element {
    // TODO: implement
  }
}
const editorConfig = {
  // ...
  nodes: [
    // Don't forget to register your custom node separately!
    CustomMentionsNode,
    {
      replace: BeautifulMentionNode, 
      with: (node: BeautifulMentionNode) => {
        return new CustomMentionsNode(
          node.getTrigger(),
          node.getValue(),
          node.getData(),
        );
      }
    }
  ]
}

The plugin also provides a helper method that overrides the default BeautifulMentionNode and uses a customized version with a component of your choice:

const CustomMentionComponent = forwardRef<
  HTMLDivElement,
  BeautifulMentionComponentProps<MyData>
>(({ trigger, value, data: myData, children, ...other }, ref) => {
  return (
    <div {...other} ref={ref} title={trigger + value}>
      {value}
    </div>
  );
});
const editorConfig = {
  // ...
  nodes: [...createBeautifulMentionNode(CustomMentionComponent)],
};

Custom menu and menu item component


function CustomMenu({ loading, ...props }: BeautifulMentionsMenuProps) {
  <ul
    className="m-0 mt-6 ..."
    {...props}
  />
}

const CustomMenuItem = forwardRef<
  HTMLLIElement,
  BeautifulMentionsMenuItemProps
>(({ selected, item, ...props }, ref) => (
  <li
    className={`m-0 flex ... ${selected ? "bg-gray-100 ..." : "bg-white ..."}`}
    {...props}
    ref={ref}
  />
));

// ...

<BeautifulMentionsPlugin
  items={mentionItems}
  menuComponent={CustomMenu}
  menuItemComponent={CustomMenuItem}
/>

Additional metadata

Additional metadata can be used to uniquely identify mentions by adding an id or any other unique property to the mention items. When serializing the editor content, the metadata will be included in the mention nodes:

const mentionItems = {
  "@": [
    { value: "Catherine", id: "1", email: "[email protected]" },
    { value: "Catherine", id: "2", email: "[email protected]" },
    // ...
  ],
};

Serializes to the following lexical nodes:

 [
   {
     "trigger": "@",
     "value": "Catherine",
     "data": {
       "id": "1",
       "email": "[email protected]"
     },
     "type": "beautifulMention",
     "version": 1
   },
   {
     "trigger": "@",
     "value": "Catherine",
     "data": {
       "id": "2",
       "email": "[email protected]"
     },
     "type": "beautifulMention",
     "version": 1
   }
 ]

All additional metadata are available as props of the BeautifulMentionsMenuItem component:

const CustomMenuItem = forwardRef<
  HTMLLIElement,
  BeautifulMentionsMenuItemProps
>(({ item: { data: { id, email }}, ...props }, ref) => (
 <li
  // ...
 />
));

Empty state

The plugin allows you to display a custom component when the mention menu is empty and no search results are found:

const Empty = () => (
  <div className="top-[2px] m-0 min-w-[10rem] overflow-hidden ...">
    No results found.
  </div>
);

// ...

<BeautifulMentionsPlugin
  // ...
  menuComponent={CustomMenu}
  menuItemComponent={CustomMenuItem}
  emptyComponent={Empty} // ๐Ÿ‘ˆ
/>

Programmatically insert, delete, or rename mentions

import {
  BeautifulMentionsPlugin,
  useBeautifulMentions,
} from "lexical-beautiful-mentions";

// ...

function MentionsToolbar() {
  const { removeMentions, insertMention } = useBeautifulMentions();
  return (
    <div className="grid gap-2 grid-cols-2">
      <Button onClick={() => removeMentions({ trigger: "#", value: "urgent" })}>
        Remove Mention
      </Button>
      <Button onClick={() => insertMention({ trigger: "#", value: "work" })}>
        Insert Mention
      </Button>
    </div>
  );
}

// ...

return (
  <LexicalComposer>
    {/** ... */}
    <BeautifulMentionsPlugin
      items={mentionItems}
    />
    <MentionsToolbar />
    {/** ... */}
  </LexicalComposer>
);

Creating new mentions

By default, the plugin allows users to create new mentions that are not in the suggestion list.

Customize the text of the menu item that allows users to create new mentions:

<BeautifulMentionsPlugin
  items={mentionItems}
  creatable={`Add user "{{name}}"`} // ๐Ÿ‘ˆ the `{{name}}` placeholder contains the current search query
/>

Hide the menu item that allows users to create new mentions:

<BeautifulMentionsPlugin
  items={mentionItems}
  creatable={false} // ๐Ÿ‘ˆ hide the menu item that allows users to create new mentions
/>

Async query function

const queryMentions = async (trigger: string, query: string) => {
  const response = await fetch(
    `https://example.com/api/mentions?trigger=${trigger}&query=${query}`
  );
  const data = await response.json();
  return data as string[];
};

// ...

return (
  <LexicalComposer>
    {/** ... */}
    <BeautifulMentionsPlugin
      triggers={["@", "#"]} // needed to tell the plugin when to call the query function
      onSearch={queryMentions}
    />
    {/** ... */}
  </LexicalComposer>
);

Mention Detection

You can customize the regular expression that looks for mentions in your text by using the punctuation and preTriggerChars properties.

Punctuation

The punctuation property allows you to specify the punctuation characters that can appear directly after a mention. So that you can type for example @Alice. without the dot being part of the mention. The default value contains a common set of punctuation characters.

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:

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:

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.

lexical-beautiful-mentions's People

Contributors

andyzg avatar circlingthesun avatar github-actions[bot] avatar matusca96 avatar reekystive avatar renovate[bot] avatar sodenn avatar vekaev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

lexical-beautiful-mentions's Issues

Intermittent Incorrect Positioning of Mention Menu in Multi-line Textarea

Describe the bug
In the lexical-beautiful-mentions plugin, the menu list sometimes appears in the wrong position when used in a multi-line textarea.

Expected behavior
The menu list should consistently appear right below the line where the mention is being typed, regardless of whether the textarea is single-line or multi-line.

Screenshots
Wrong:
image
Correct:
image

Metadata lost when copying & pasting nodes

I encountered an issue where copying a node that contains metadata and then pasting it results in the pasted node lacking its associated metadata. Attempts to retrieve the metadata using node.getData() was unsuccessful, as it consistently returned undefined.
image

Possibility to pass an object instead of string for suggestion

Hi, first of all thanks for this helpfull plugin library!

I would like to know if it's possible to pass an object in the items property, something like:

'@': [
    { name: 'Anton', email: '[email protected]' },
    { name: 'Boris', email: '[email protected]' },
    { name: 'Catherine', email: '[email protected]' },
    { name: 'Dmitri', email: '[email protected]' },
    { name: 'Elena', email: '[email protected]' },
  ],
  '\\w+:': [],
};

I've tested and actually works, but not 100%, because I think the whole plugin is expecting just a string instead of an object. So in my menu item component I had to change it a little bit:

type SuggestionProps = Omit<BeautifulMentionsMenuItemProps, "children"> & { children: { name: string; email: string; } };

const Suggestion = forwardRef<HTMLLIElement, SuggestionProps>(({ children: { name, email }, ...rest }, ref) => {
  return (
    <li {...rest} aria-label={`Choose ${name}`} ref={ref}>
      <span>{name}</span>
      <span>{email}</span>
    </li>
  );
});

But this results in some unexpected behaviors, for example the onClick in the item, adds a [Object object] in the editor, if we try to filter a mention before selecting in the menu, for example "Hey @Ant", after the @ char, throws an error because is trying to filter based on a string.

So I'm not sure if this is possible, or if there is any other way to pass additional props to the menu item component instead of just a string.

Thanks a lot!

when removing hashtag or mention Close keyboard in android

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Go to 'type hashtag or mention'
  2. Write @ or # trigger to show Recommend List items
  3. delete that
  4. See error

Expected behavior
when removing hashtag or mention Close keyboard in android

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: android
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: xiaomi note 11
  • OS: android 12
  • Browser google chrome
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

be able to type mention additional data

Is your feature request related to a problem? Please describe.
when implementing a custom mention node like in the example - https://github.com/sodenn/lexical-beautiful-mentions#custom-mention-node-and-component, you end up with code like

const CustomMentionComponent = forwardRef<HTMLSpanElement, BeautifulMentionComponentProps>(
  ({ trigger, value, data, children, ...other }, ref) => {

where data is any.

Describe the solution you'd like
It would be nice to do something like

const CustomMentionComponent = forwardRef<HTMLSpanElement, BeautifulMentionComponentProps<DataType>>(
  ({ trigger, value, data, children, ...other }, ref) => {

so data is of type DataType

Describe alternatives you've considered
Otherwise, you end up with a not quite right BeautifulMentionComponentProps & {data: MentionData}

onSelectOption prop

Is your feature request related to a problem? Please describe.
Is it possible to provide our custom own handleSelectOption function to the plugin which can replace this function? I want to be able to customize how some mention types are displayed.

Describe the solution you'd like
It can be a prop similar to onMenuClose/onMenuOpen like onSelectOption with the following signatures:

(
  selectedOption: MenuOption,
  nodeToReplace: TextNode | null,
  closeMenu?: () => void,
) => void

just as it is currently.

Describe alternatives you've considered
I considered defining a custom mention component but my specific problem cannot be solved by that.

Cursor jumps to the beginning of the input on Safari

Describe the bug
After selecting a mentioned users, the cursor visually jumps the the beginning of the input.

To Reproduce
Steps to reproduce the behavior:

  1. Type some text and mention somebody by typing @ in the input.
  2. Select a person from the list.
  3. The cursor will automatically jump to the beginning of the input (visually).
  4. If continue typing, the cursor will jump the end and work normally.

Expected behavior
The cursor should stay at the end after selecting a mentioned person.

Screenshots
The cursor visually jumps to the start after selecting a person:
image
Ignore that and continue typing:
image

Desktop (please complete the following information):

  • OS: MacOS
  • Browser: Safari
  • Version: 15.6

ZeroWidthPlugin causes entire page to freeze

Describe the bug
Inclusion of ZeroWidthPlugin causes entire page to freeze when the last node is a decorator (e.g. a mention).

To Reproduce

  1. Setup editor with mentions and ZeroWidthPlugin
  2. Add a mention at end of editor
  3. Page freezes

Expected behavior
Page continues working normally.

Desktop (please complete the following information):

  • OS: mac
  • Browser: latest chrome and safari

Additional context

  • Lexical: 0.13.1
  • lexical-beautiful-mentions: 0.1.29 & 0.1.30
  • react: 18.2.0

When I comment out ZeroWidthPlugin the issue goes away. Not sure what's different for me vs the functional demo @ https://lexical-beautiful-mentions-docs.vercel.app/ ... what versions of libraries is that running?

`convertToMentionNodes` Function Incorrectly Converts Certain Strings to Variables

Describe the bug
In the lexical-beautiful-mentions plugin, the convertToMentionNodes function is incorrectly converting certain strings (like email addresses) into variables. This is not the expected behavior, as these strings should not be treated as variables.

To Reproduce
CodeSandbox link.

Expected behavior
The convertToMentionNodes function should not convert these strings into variables. They should be treated as plain text.

image

Enhanced Control Over `creatable` Feature

Is your feature request related to a problem? Please describe.
Yes, currently in the lexical-beautiful-mentions plugin, the creatable feature has limited flexibility. I'm unable to handle the creation of new variables or customize the component for creating new variables. Additionally, I want the menu with the create button to be displayed all the time when a user writes '@'.

Describe the solution you'd like
I would like to have enhanced control over the creatable feature. Specifically, I would like to:

  1. Handle the onCreate event for new variables.
  2. Create my own component for creating new variables.
  3. Show the menu with the create button in the menu all the time when a user writes '@'.
  4. Handle the length or pattern of a new variable.

Describe alternatives you've considered
I've considered managing the creation of new variables outside of the lexical-beautiful-mentions plugin, but this approach is not efficient and can lead to inconsistencies.

Additional context
This feature would be beneficial for scenarios where custom handling or presentation of the creation of new variables is required. It would provide more flexibility and control to the developers using the lexical-beautiful-mentions plugin.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

dockerfile
www/Dockerfile.e2e
  • mcr.microsoft.com/playwright v1.46.1-jammy
github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • actions/setup-node v4
  • actions/upload-artifact v4
  • actions/checkout v4
  • actions/setup-node v4
  • actions/download-artifact v4
  • actions/upload-artifact v4
  • actions/checkout v4
  • actions/setup-node v4
  • mcr.microsoft.com/playwright v1.46.1-jammy
.github/workflows/codeql-analysis.yml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • github/codeql-action v3
  • github/codeql-action v3
  • github/codeql-action v3
.github/workflows/inactive-issues.yml
  • actions/stale v9
.github/workflows/publish.yml
  • actions/checkout v4@692973e3d937129bcbf40652eb9f2f61becf3332
  • actions/setup-node v4
npm
package.json
  • @changesets/cli 2.27.7
  • @typescript-eslint/eslint-plugin 7.18.0
  • @typescript-eslint/parser 7.18.0
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-config-turbo 2.0.14
  • eslint-plugin-react 7.35.0
  • eslint-plugin-react-hooks 4.6.2
  • prettier 3.3.3
  • prettier-plugin-organize-imports 4.0.0
  • prettier-plugin-tailwindcss 0.6.6
  • turbo 2.0.14
  • typescript 5.5.4
  • npm 10.8.2
plugin/package.json
  • @testing-library/react 16.0.0
  • fast-glob 3.3.2
  • happy-dom 15.0.0
  • npm-run-all2 6.2.2
  • rimraf 6.0.1
  • vite 5.4.2
  • vitest 2.0.5
  • @lexical/react >=0.11.0
  • @lexical/utils >=0.11.0
  • lexical >=0.11.0
  • react >=17.x
  • react-dom >=17.x
www/package.json
  • @lexical/react 0.17.0
  • @lexical/utils 0.17.0
  • @radix-ui/react-checkbox 1.1.1
  • @radix-ui/react-dropdown-menu 2.1.1
  • @radix-ui/react-separator 1.1.0
  • @radix-ui/react-slot 1.1.0
  • @radix-ui/react-tooltip 1.1.2
  • @tailwindcss/forms 0.5.7
  • class-variance-authority 0.7.0
  • clsx 2.1.1
  • dompurify 3.1.6
  • lexical 0.17.0
  • lucide-react 0.435.0
  • next 14.2.6
  • next-themes 0.3.0
  • react 18.3.1
  • react-dom 18.3.1
  • tailwind-merge 2.5.2
  • tailwindcss 3.4.10
  • tailwindcss-animate 1.0.7
  • @babel/core 7.25.2
  • @playwright/test 1.46.1
  • @types/dompurify 3.0.5
  • @types/node 20.16.1
  • @types/react 18.3.4
  • @types/react-dom 18.3.0
  • autoprefixer 10.4.20
  • eslint-config-next 14.2.6
  • playwright 1.46.1
  • postcss 8.4.41

  • Check this box to trigger a request for Renovate to run again on this repository

Example of hydrating and dehydrating display values

Thank you for the great work on this plugin ๐Ÿ™

Is your feature request related to a problem? Please describe.
This might be more of a Lexical question than it is a plugin question but is there any functionality that supports "hydrating" the mentions? This is particularly useful for letting users update their display name and serializing & deserializing can simply use the metadata to fetch the latest name as the default value. I.e. When serializing, get all mentions in the editor and set its display value to null. When deserializing, programmatically set the display value of the mention based on the data.id attribute set in the metadata of the mention.

Describe the solution you'd like
Functions to programmatically update the display value of a mention based on the ID of the mention.

Describe alternatives you've considered
I have tried using renameMentions but the "value" parameter doesn't appear to support filtering on metadata fields

Keyboard is closed when trying to delete mention

Describe the bug
When trying to delete the mention, it doesn't work and I also lose focus to the input, causing the keyboard to be closed.

To Reproduce
Steps to reproduce the behavior:

  1. Go to 'Demo page'
  2. Type @ to see the mentions list and pick one
  3. Try to delete the mention
  4. It doesn't work and I lose focus to the input

Expected behavior
The mention should be deleted and I still keep focus on the input

Video
https://github.com/sodenn/lexical-beautiful-mentions/assets/64638082/e519a3e7-c619-425e-bce3-a534cdb09609

Smartphone (please complete the following information):

  • Device: Xiaomi Redmi K40 Gaming
  • OS: Android 13
  • Browser: Chrome

Additional context
The bug only happens on Android phone's Chrome (Xiaomi specifically)

Error : I encountered an error when I followed the usage process

Error screenshot

When i click the insertMention to get this error
image

source code

  • Add MentionsToolbar
import { GreetingMessageAPIFormat } from "@/types/greetingMessage";
import { chakraColors } from "@/utils/chakraTheme";
import { Button, HStack, Text } from "@chakra-ui/react";
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";

import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { $createParagraphNode, $createTextNode, $getRoot } from "lexical";
import { useState } from "react";

import { HashtagPlugin } from "@lexical/react/LexicalHashtagPlugin";
import {
  BeautifulMentionsPlugin,
  useBeautifulMentions,
} from "lexical-beautiful-mentions";
import { EditorStateProps } from "..";
import EditorNodes from "./nodes/EditorNodes";
import AutoFocusPlugin from "./plugins/AutoFocusPlugin";
import { MaxLengthPlugin } from "./plugins/MaxLengthPlugin";
import editorTheme from "./themes/Theme";

type LexicalEditorProps = {
  editorState: EditorStateProps;
  setEditorState: React.Dispatch<
    React.SetStateAction<GreetingMessageAPIFormat | undefined>
  >;
};

function MentionsToolbar() {
  const { removeMentions, insertMention } = useBeautifulMentions();
  return (
    <div className="grid gap-2 grid-cols-2">
      <Button onClick={() => removeMentions({ trigger: "#", value: "urgent" })}>
        Remove Mention
      </Button>
      <Button onClick={() => insertMention({ trigger: "#", value: "work" })}>
        Insert Mention
      </Button>
    </div>
  );
}

export default function LexicalEditor({
  editorState,
  setEditorState,
}: LexicalEditorProps) {
  const MAX_TEXT_LIMIT_LENGTH = 500;
  const [textLength, setTextLength] = useState(0);

  const prepopulatedRichText = () => {
    if (!editorState || editorState.msg_type === "IMAGE") return;
    const root = $getRoot();
    const { message } = editorState;

    if (root.getFirstChild() === null) {
      const heading = $createParagraphNode();
      heading.append($createTextNode(message));
      root.append(heading);
    }
  };

  const initialConfig = {
    editorState: prepopulatedRichText,
    namespace: "MyEditor", // ๅ‘ฝๅๆฒ’ๆœ‰็‰นๆฎŠๅซ็พฉ๏ผŒไฝ†้œ€่ฆ็ตฆไบˆไธ€ๅ€‹ๅ‘ฝๅ
    theme: editorTheme,
    nodes: EditorNodes,
    onError: (error: Error) => {
      throw error;
    },
  };
  const mentionItems = {
    "@": ["Anton", "Boris", "Catherine", "Dmitri", "Elena", "Felix", "Gina"],
    "#": ["Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape"],
    "due:": ["Today", "Tomorrow", "01-01-2023"],
  };

  return (
    <LexicalComposer initialConfig={initialConfig}>
      <div className="editor-container">
        <PlainTextPlugin
          contentEditable={<ContentEditable className="editor-input" />}
          placeholder={<Placeholder />}
          ErrorBoundary={LexicalErrorBoundary}
        />

        <OnChangePlugin
          onChange={editorState => {
            editorState.read(() => {
              const root = $getRoot();
              const editorText = root.getTextContent();
              const editorTextLength = editorText.length;
              setEditorState(prev => {
                if (prev && prev.msg_type === "TEXT") {
                  return {
                    ...prev,
                    message: editorText,
                  };
                }
                return prev;
              });
              setTextLength(editorTextLength);
            });
          }}
        />
        <BeautifulMentionsPlugin items={mentionItems} />
        <MentionsToolbar />

        <AutoFocusPlugin />
        <MaxLengthPlugin maxLength={MAX_TEXT_LIMIT_LENGTH} />
        <HashtagPlugin />
      </div>
      <HStack justifyContent="flex-end">
        <Text fontSize="sm" color={chakraColors.gray[2]}>
          {textLength}/{MAX_TEXT_LIMIT_LENGTH}
        </Text>
      </HStack>
    </LexicalComposer>
  );
}

function Placeholder() {
  return <div className="editor-placeholder">Enter some plain text...</div>;
}

Mentions don't showing properly when setting editor initial state.

In my case, I'm using this library to simulate template variables (e.g @[VARIABLE]) which has worked well so far.

So as I mentioned, the problem begins when I try to set a initial state to editor, in order to load some back-end data which contains the HTML to be rendered in the editor, as you see below:

const initializeEditorState = (editor) => {
    // defaultValue is the data that comes from back-end
    if (defaultValue) {
       const parser = new DOMParser();
       const dom = parser.parseFromString(defaultValue, "text/html");
       const nodes = $generateNodesFromDOM(editor, dom);

       $getRoot().select();
       $insertNodes(nodes);
    }
};

const initialConfig = {
    namespace: 'LetterEditor',
    theme: editorTheme,
    onError: (error) => console.error(error),
    editorState: initializeEditorState,
    nodes: [
        HeadingNode,
        ListNode,
        ListItemNode,
        QuoteNode,
        ...createBeautifulMentionNode(CustomMentionComponent),
    ]
};

<LexicalComposer initialConfig={initialConfig}>
    <ToolbarPlugin />
    <RichTextPlugin
        contentEditable={<ContentEditable className="editor" />}
        placeholder={<div className="editor-placeholder">Escribe...</div>}
        ErrorBoundary={LexicalErrorBoundary}
    />
    <HistoryPlugin />
    <ListPlugin />
    <BeautifulMentionsPlugin
         items={LETTER_VARIABLES}
         triggers={["@"]}
    />
    <OnChangePlugin onChange={onChange} ignoreSelectionChange />
    <MyCustomAutoFocusPlugin />
</LexicalComposer>

I noticed whenever parsed dom with the defaultValue pass through $generateNodesFromDOM(), this last function erases all the metadata and styles added by ...createBeautifulMentionNode(CustomMentionComponent).

How I saved it on create:

<p class="editor-paragraph" dir="ltr" style="text-align: center;"><b><strong class="editor-text-bold" style="white-space: pre-wrap;">GREETINGS TO YOU</strong></b><span style="white-space: pre-wrap;"> </span><span data-lexical-beautiful-mention="true" data-lexical-beautiful-mention-trigger="@" data-lexical-beautiful-mention-value="[DD]">@[DD]</span><span style="white-space: pre-wrap;">/</span><span data-lexical-beautiful-mention="true" data-lexical-beautiful-mention-trigger="@" data-lexical-beautiful-mention-value="[MM]">@[MM]</span><span style="white-space: pre-wrap;">/</span><span data-lexical-beautiful-mention="true" data-lexical-beautiful-mention-trigger="@" data-lexical-beautiful-mention-value="[AAAA]">@[AAAA]</span></p>

image

How it renders on initialize editor (passed through $generateNodesFromDOM() function).

<p class="editor-paragraph" style="text-align: center;"><b><strong class="editor-text-bold" style="white-space: pre-wrap;">GREETINGS TO YOU</strong></b><span style="white-space: pre-wrap;"> </span><span style="white-space: pre-wrap;">@[DD]</span><span style="white-space: pre-wrap;">/</span><span style="white-space: pre-wrap;">@[MM]</span><span style="white-space: pre-wrap;">/</span><span style="white-space: pre-wrap;">@[AAAA]</span></p>

image

As you can see, all metadata from metions disappeared in the HTML content, so it couldn't be rendered properly.

Expected behavior
Render the mentions (or variables in this case) correctly on initialize editor state.

Desktop (please complete the following information):

  • OS: Linux Ubuntu 22.10
  • Browser: Chrome
  • Version: 119

How could I fix this?
Or this seems to be a problem with lexical's $generateNodesFromDOM()?

Thank you for the answers and this great library :)

Error Type 'ForwardedRef<HTMLElement>' is not assignable to type 'LegacyRef<HTMLUListElement> | undefined'.

Hello and thank you so much for your work !

I'm having a problem trying to install this plugin :
When adding the Menu :
const CustomMenu = forwardRef<HTMLElement, BeautifulMentionsMenuProps>(
({ open, loading, ...props }, ref) => (
<ul className="m-0 mt-6 ..." {...props} ref={ref} />
)
);

I have this error for this first ref in ref={ref} stating that "Type 'ForwardedRef' is not assignable to type 'LegacyRef | undefined'."

Would you have an idea ?
Best regards !
Julien

Request for not showing current mentions as suggestions

Is your feature request related to a problem? Please describe.
This feature request isn't related to a problem, just really helpful for what I'm implementing. Basically today I'm trying to achieve this:

I noticed that everytime we mention someone for the suggestions menu, in the subsequent times we open the menu, it shows the current mentions as suggestions, to explain better let's get this example:

PS.: this happens to me that is using the onSearch property, not the items one, I'm fetching the data remotely.
PS2.: also in my current example, when onSearch is triggered, I'm checking if the query has 3 or more characters, if there is less then 3, so I'm returning an empty array [].

  • Trigger is the "@" symbol;
  • Type @user, and let's suppose after fetching the suggestions, I see 3 users:
    • Test user 1;
    • Test user 2;
    • Test user 3.
  • Select the user "Test user 1" in the menu;
  • It will add this user as a mention, then continue typing, until type only this: "@";
  • In this moment instead of doesn't show anything, it shows as suggestion the current mention "Test user 1";
  • Only after typing something after the trigger char, that is actually modifying the suggestions, since I'm fetching again;

Looking in the codebase, I've found in the BeautifulMentionsPlugin.tsx file, inside the editor.getEditorState().read on line 173, it's where you get all the current mentions and then add to the list of suggestions.

Describe the solution you'd like
So, what I'll be really glad if it would be possible to create an optional property to BeautifulMentionsPlugin disable this behavior whenever we want, like showCurrentMentionsAsSuggestion, in case of true, it'll show, otherwise, not.

Describe alternatives you've considered
To be honest I'm kind stuck on this, because from the onSearch function, I'm returning an empty array, but even by doing that, is getting the current mentions and adding to the suggestion menu. So I'm not sure if there is something else I can try on this.

When creating a mention the cursor goes back in the line.

When I create a new mention, the cursor goes back the beginning of the line. Any idea on why this might be happening?

MY implementation is quite simple:

`import React, { useRef, useEffect, useCallback } from 'react';
import { $getRoot,
$getSelection,
createEditorState,
$serializeEditorStateToMarkdown,
} from 'lexical';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
import { ListPlugin} from '@lexical/react/LexicalListPlugin'
import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
import { ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
import editorTheme from '../../theme/editorTheme';
import {
BeautifulMentionsPlugin,
BeautifulMentionNode
} from "lexical-beautiful-mentions";
import { MenuItem, MenuList } from '@mui/material';

const CustomMenuList = React.forwardRef(({ children, ...other }, ref) => {
return (
<MenuList ref={ref} {...other}
style={{ position: 'absolute', bottom: '100%', background:'black', borderRadius: '10px' }}>
{children}

);
});

// Custom MenuItem component
const CustomMenuItem = React.forwardRef(({ children, ...other }, ref) => {
return (
<MenuItem fullWidth ref={ref} {...other} >
{children}

);
});

const beautifulMentionsTheme = {
"@": "mention",
"#": "hashtag",
"due:": "time"
};

const editorThemeWithMentions = {
...editorTheme,
beautifulMentions: beautifulMentionsTheme,
};

const initialConfig = {
namespace: 'MyEditor',
theme: editorThemeWithMentions,
nodes: [BeautifulMentionNode],
onError: (error) => {
console.error(error);
},
};

const mentionItems = {
"@": ["Anton", "Boris", "Catherine", "Dmitri", "Elena", "Felix", "Gina"],
"#": ["Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape"],
"due:": ["Today", "Tomorrow", "This week", "Next week", "This month", "Next month", "This Quarter", "Next quarter", "This year", "Next year"],
};

const Editor = ({handleContentChange }) => {

function onChange(editorState) {
editorState.read(() => {
const root = $getRoot();
const textContent = root.getTextContent();
handleContentChange(textContent);
});
};

return (
<>

<RichTextPlugin
contentEditable={<ContentEditable style={{outline:'none', border:'none'}}/>}
placeholder={

What's in your mind?
}
ErrorBoundary={LexicalErrorBoundary}
/>
{/* */}





</>
);
};

export default Editor;`

Custom Regex Rule for Variable Creation

Is your feature request related to a problem? Please describe.
Currently, in the lexical-beautiful-mentions plugin, the pattern for creating new variables is not flexible enough. I want to create variables that only include English characters, numbers, and the '_' symbol, but the current library implementation does not support this. (ex. @first_name)

Describe the solution you'd like
I would like to have the ability to define a custom regex rule for variable creation. This would allow me to enforce a specific pattern for variables.

Describe alternatives you've considered
I've considered using the existing pattern options, but they do not provide the flexibility I need for my specific use case.

Additional context
This feature would be beneficial for scenarios where developers need to enforce a specific naming convention for variables. It would provide more flexibility and control to the developers using the lexical-beautiful-mentions plugin.

What can I do to optimize performance when my editor is updated every time?

I created a custom suite to calculate the text length and editor content, and pass it out to the outer state, so that my Page can display specific UI according to these states

When the editor preset mount is completed, I will give the editor a piece of initialValue data, and at this time I will pass the data to the parent layer's Page to update the state

I am now adding a listener. Every time I change the content of the editor, I will update these states to the parent layer, but I suspect that this approach will cause performance problems. Is there a recommended way to fix it?

CustomPlugin source code

import { GreetingMessageAPIFormat } from "@/types/greetingMessage";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $getRoot, $nodesOfType, EditorState } from "lexical";
import { MentionNode } from "../nodes/MentionNode";
import { convertTextContent } from "../utils/mention-utils";

type ComputedTextLengthPluginProps = {
  setTextLength: (textLength: number) => void;
  setEditorState: React.Dispatch<
    React.SetStateAction<GreetingMessageAPIFormat | undefined>
  >;
};
const calculateTotalMentionLength = (mentionNodes: MentionNode[]) =>
  mentionNodes.reduce(
    (totalLength, current) => totalLength + current.getTextContent().length,
    0,
  );

export default function ComputedTextLengthPlugin({
  setTextLength,
  setEditorState,
}: ComputedTextLengthPluginProps) {
  const [editor] = useLexicalComposerContext();

  const handleEditorStateChange = (editorState: EditorState) => {
    editorState.read(() => {
      const rootElement = $getRoot();
      const entireContentLength = rootElement.getTextContent().length;

      // Calculate the length of the mentions within the text
      const mentionNodes: MentionNode[] = $nodesOfType(MentionNode);
      const mentionNodesPlaceholderLength = mentionNodes.length * 20; // Assuming each mention placeholder has a fixed length of 20
      const totalMentionLength = calculateTotalMentionLength(mentionNodes);

      // Determine the text length excluding mentions, accounting for human-computed length
      const nonMentionTextLength =
        entireContentLength -
        totalMentionLength +
        mentionNodesPlaceholderLength;
      setTextLength(nonMentionTextLength);

      // Convert the editor's content and update the state
      const textContent = convertTextContent(rootElement);
      setEditorState(previousState => {
        if (previousState && previousState.msg_type === "TEXT") {
          return { ...previousState, message: textContent };
        }
        return previousState;
      });
    });
  };

  // TODO: It should be wrapped with an editor to monitor the editor's changes to update, otherwise it is every update now, which may cause performance problems
  editor.registerUpdateListener(({ editorState }) => {
    editor.update(() => {
      handleEditorStateChange(editorState);
    });
  });

  return null;
}

Custom Mention Node

Great work on this plugin, works like a charm.

  1. Control rendering of the mention, beyond just styles. I want to add event handlers, so that I can display a tooltip on hover or a popover on click.

There is an option to override the behavior of Lexical Nodes. So you can override the default BeautifulMentionNode class and create your own BeautifulMentionNodeWithTooltip class. See https://lexical.dev/docs/concepts/node-replacement

Originally posted by @sodenn in #101 (comment)

I've been looking into this and I'm not sure how to do it. A param for custom node would be great, or otherwise some documentation on how to do it would properly also would help me.

Allow "/" as a mention trigger

Is your feature request related to a problem? Please describe.
I want to build a text editor which support Slack-like forward slash "/" commands but this check prevents me from using "/" as a trigger.

Describe the solution you'd like
Is it possible if this check is optional? Would it be as simple as adding an additional boolean prop field "checkSlash" to BeautifulMentionsPluginProps?

Describe alternatives you've considered
I have considered "//" or "/:" but they are pretty unusual replacements. I am already using @ and # for different kinds of mentions.

Additional context
I just want to say that this package is amazing! Truly a life-saver for a front-end beginner like me :)

Styled components default styling support

Is your feature request related to a problem? Please describe.
Styles are not working when project is not configured to use tw, mine has styled components

Describe the solution you'd like
Add styled components option to inherit default styles instead of making custom components

Describe alternatives you've considered
Currently im using the custom components to override which is an awesome feature but creates more boilerplate

Additional context
Awesome library!

`OPEN_MENTION_MENU_COMMAND` Doesn't to Open Menu Without Preceding Space When Selection Begins with a Symbol

Describe the bug
In the lexical-beautiful-mentions plugin, the OPEN_MENTION_MENU_COMMAND does not add a space before the selection. This causes an issue where the mention menu does not open as expected.

To Reproduce
Steps to reproduce the behavior:

  1. Go to a page with a textarea using lexical-beautiful-mentions.
  2. Try to open the mention menu without adding a space before the selection.
  3. Notice that the mention menu does not open.

Expected behavior
The OPEN_MENTION_MENU_COMMAND should add a space before the selection, allowing the mention menu to open correctly.

A few keyboard shortcuts don't work around mention nodes.

Describe the bug
Some keyboard shortcuts don't work with mentions.

To Reproduce
Cannot highlight preceding text when to the right of a mention node:

  1. Go to https://lexical-beautiful-mentions-docs.vercel.app/
  2. With the cursor immediately to the right of a mention at the end of the document (should be last node), try highlighting all preceding text with, CMD+SHIFT+UP_ARROW.
  3. Notice the highlight flashes but doesn't stay. Interestingly this works if there's other text after the cursor.

Cannot jump to start or end of text line:

  1. Go to https://lexical-beautiful-mentions-docs.vercel.app/
  2. Put cursor within text between two mentions.
  3. Hit control-A and control-E
  4. Notice cursor jumps to start & end of current text node but not start/end of paragraph (text line) as it should

Expected behavior
Mention nodes have no impact on normal keyboard shortcuts for text editing.

Additional context
From https://support.apple.com/en-us/HT201236

Control-A: Move to the beginning of the line or paragraph.
Control-E: Move to the end of a line or paragraph.

How can i ignore the mention textLength?

I am working on computing the text length within an editor, but I would like to exclude the length of mentions. In other words, I want to calculate the length of the text content without including the characters related to mentions.

Here is the code snippet I am currently using to compute the text length:

        <OnChangePlugin
          onChange={editorState => {
            editorState.read(() => {
              const root = $getRoot();
              const editorText = root.getTextContent();
              const editorTextLength = editorText.length;
              console.log(editorText.length)
            });
          }}
        />

How can I modify this to ignore the length of mentions within the text content?
Any guidance or suggestions would be greatly appreciated !

Registering another KEY_ENTER_COMMAND blocks the menu from closing

Is your feature request related to a problem? Please describe.
I use lexical and your plugin to build a chat like application. To send messages I registered a KEY_ENTER_COMMAND with low priority which blocks further command execution. But now when I add a mention and I try to select a mention item with enter my command gets executed first and prevents the mention plugin from closing the menu and adding the selected mention

Describe the solution you'd like
Raise the command priority to medium

Describe alternatives you've considered
Expose the command priority as a prop

Customizing the mention's rendering and metadata

First of all, thank you for open sourcing this! This is the most fully-featured open-source mentions implementation I've come across so far.

Two questions:

  1. Is there anyway to get a ref to the mention tags that are created in the editor, or attach an event listener to it, or swap it out for another React component?

  2. Is there a way I can add additional metadata to each mention (a unique ID, for instance), and have the editor track it so I can later access it when serializing the editor state? Otherwise, how can I access the state of the mentions tracked by the extension?

I see there's a way to customize the menu and menu items in the suggestions dropdown, but how can I customize the mentions rendered in the editor?

Iโ€™m trying to build an editor where users can create "variables" in a similar way to mentions. I need to track (using a unique ID) of each variable so I can save it to the database later. I also want to add additional metadata that users can define (such as a default value for the variable).

Convert to Mention Nodes with Data from Mention Items

Describe the bug
The convertToMentionNodes function in lexical-beautiful-mentions does not support converting mention items along with their associated data.

To Reproduce
Steps to reproduce the behavior:

  1. Use the convertToMentionNodes function with mention items that have associated data. (ex. [{ id: '1', value: 'foo', color: 'red' }])
  2. The function converts the mention items but does not include the associated data.

Expected behavior
I expect the convertToMentionNodes function to convert mention items along with their associated data. This would make it more flexible and useful in scenarios where the data associated with mention items is crucial.

Additional context
This feature would be beneficial for scenarios where mention items carry additional data that is important when they are converted to mention nodes.

Trying to use custom rich text toolbar with beautiful-mentions causes editor to lose focus

Describe the bug
I'm building a custom rich text editor. It needs to feature basic rich text formatting and custom variables. I've found that lexical-beautiful-mentions is perfect for adding variables, however after implementing it and the custom formatting toolbar I've found that, once I add a mention, when I try to use the toolbar to format the text it seems the editor loses focus and the selection object within editorState returns null.

To Reproduce
I've managed to reproduce the bug within a sandbox: https://codesandbox.io/p/devbox/55dyz5

  1. Type any text on the editor
  2. Click on the buttons bold or italic
  3. It should format the selected text accordingly

The bug:

  1. Now add a mention using the trigger '{{'
  2. Select any text other than the mention
  3. Click the buttons bold or italic
  4. The editor will lose focus/selection and not format the text accordingly

Expected behavior
I expect the buttons with the formatting options to continue working after I'ved added a mention.

Additional context
It is still possible to format text using keyboard commands, which leads me to believe that the problem is in focus/selection. Really appreciate any help trying to solve this.

Case of use: variables

i am using this extension to enable user insert variables in the following format: {variable}. So thats why i have to use a CustomMentionComponent to be able to render the brackets '{}' before/after.
However whenever I save or copy the value in the clipboard, the value that is being saved is still __trigger+__value, which would result {mentionItem .... can this be replaced?

Space character in not inserted automatically when triggers props is empty

Describe the bug

Sometimes I don't want to use mention suggestions, but prefer to programmatically insert mentions elsewhere (for example, using a custom mention button). I do this by setting triggers to empty.

  <BeautifulMentionsPlugin ... triggers={[]} />

However, the automatic spaces insertion stopped working when inserting a mention node or typing text after a mention node after setting triggers props to empty.

It's not just about the spaces themselves. As the presence of zero-width spaces, this also causes some IME problems (e.g. Chinese input methods), see the video below.

To Reproduce

  • Set triggers props to [].

      <BeautifulMentionsPlugin ... triggers={[]} />
  • Insert a mention node programmatically.

  • Insert a word character after a mention node, e.g. a, b, c.

  • There's no space inserted automatically after the mention node.

Expected behavior

  • There's a space character inserted automatically after the mention node.

Screen recordings

Actual behavior

Screen.Capture.2024-04-10.at.15.22.06-trimmed.mp4

Expected behavior

Screen.Capture.2024-04-10.at.15.40.22-trimmed.mp4

Desktop

  • OS: macOS Version 14.4.1 (Build 23E224)
  • Browser: Chrome 123.0.6312.107 (Official Build) (arm64)
  • Version: lexical-beautiful-mentions 0.1.33

Copy paste mentions fails with PlainTextPlugin

Describe the bug
Copy paste of mentions does not work with PlainTextPlugin

To Reproduce
Steps to reproduce the behavior:

  1. Use PlainTextPlugin
  2. Copy and paste text which includes a mention.
  3. Notice it pastes as plain and not with the mention.

Expected behavior
Expect pasting to include the mention decoration.

Applying formatting to selected content that contains a mention can fail

Describe the bug
Using the Lexical Rich Text sample app, applying formatting from the toolbar fails when selecting text content along with a mention. This is only seen when the formatting is applied using the toolbar. Using keyboard shortcuts (i.e. CTRL-B for bold) will work.

To Reproduce
Steps to reproduce the behavior:

  1. Using this codesandbox, enter some text and add a mention somewhere within this text. The linked codesandbox is a clone of the lexical Rich Text sample with a minimal configuration of the lexical-beautiful-mentions plugin.
  2. Select the mention along with some of the text content.
  3. Click on the Bold toolbar button.
  4. See that the selection has been removed and the formatting has not been applied.

Expected behavior
In step 4, expected the selection to be preserved and that the formatting applied to the text node(s).

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser Chrome
  • Version 122.0.6261.112

Screenshots
After step 4 using lexical-beautiful-mentions:
image

After step 4 using Lexical playground (as a comparison):
image

Additional context
Using the Lexical playground as an example with their mention plugin, it is able to support the toolbar format buttons using the editor.dispatchCommand(FORMAT_TEXT_COMMAND, xxx) calls. I suspect a key difference is their mention node inherits from TextNode rather than DecoratorNode.

Allow null or object for BeautifulMentionsItem

Hey! Love the repo. Wondering what your thoughts are on allowing null or object for type BeautifulMentionsItem? Here's a use case (and feel free to lmk the correct way if this doesn't make sense):

I want to show tiny profile avatars in the @ trigger node. Some users have avatars, some don't. Would be great to pass null for that data instead of the empty string (what I'm currently doing).

Does this make any sense? Lmk! Thanks!

Typeahead menu is not unmounted when the filter word produces no candidates

Describe the bug
Typeahead menu is not unmounted when the filter characters after the trigger character produces no candidates in the typeahead menu.

To Reproduce
Steps to reproduce the behavior:

  1. Type '/' (my trigger character) to bring up the typeahead menu, which contains a few items.
  2. Keep typing until there is no item matching the filter, and the typeahead menu becomes invisible.
  3. Notice that the <div id="typeahead-menu" is still in the DOM, and that the attribute aria-controls="typeahead-menu" is still on the content editable, although the popup is already invisible.
  4. Also, the KEY_DOWN_COMMAND listeners I registered in other plugins (no matter the priority) are not invoked when the typeahead menu is in this state. I think the BeautifulMentions plugin's command listeners are preventing other plugin's from running.

Expected behavior
The typeahead menu div should be removed from the DOM, and my other KEY_DOWN_COMMAND listeners should work.

Screenshots

Desktop (please complete the following information):

  • OS: Windows 11 and latest macOS
  • Browser: Chrome
  • Version: 118.0.5993.118

Additional context

InsertMention should support optional data

Describe the solution you'd like
The insertMention helper provided by useBeautifulMentions should support passing in data for mentions that contain things like id/email.

Describe alternatives you've considered
I'll need to handle inserting myself for now but I'd much rather let this library do that!

Pass metadata to getMentions function

Is your feature request related to a problem? Please describe.
This is not related to a problem, it's just really helpful for what I'm implementing. Basically after adding the new functionality of being able to add a metadata to the suggestions, I would like to when calling the getMentions function from useBeautifulMentions hook, I can well retrieve this data, today when we trigger this function, we only get an array with this interface:

interface Test {
    trigger: string;
    value: string;
}[]

Describe the solution you'd like
So, the solution I would like is to add a new prop on this response that is the same data I've passed when returning the data from onSearch function or from the items property.

Describe alternatives you've considered
The alternative on this is a very specific scenario, because I'm implementing an application that we are storing comments, and when I try to save, I need to send more properties than just the name of the person who has been mentioned, I need for example the e-mail and some other add. info that I'm well passing to the metadata.

In order to fix this for now, I've created a react context, where everytime I click in a suggestion, I'm also saving this data in a list in my context, so in anytime and any place I need these additional info of a corresponding mention, I can use the list from the context.

Additional context
Maybe there would be a different solution for this, but I couldn't achieve this.

Append React 17 as a peer dependency

First of all I'd like to thank you for an awesome plugin.

We (unfortunately) still use React 17 for many projects at work and we would really like to use this plugin.
Obviously, we can just force install it and ignore the warnings, but I'm not sure it's even necessary to demand React 18 in the first place.

Furthermore, when I did force-install, I've encountered 2 browser warnings with a project following the README.md example code.

image

At that point I wasn't sure if these were attributed to us using React 17 or are just errors in the package.

To test this, I've played with a version of this repo (https://github.com/yamcodes/lexical-better-mentions) that is the same as this one, with a number of changes:

  1. Set React (and react-dom) 17 as the minimal peerDependency version
  2. Merging the LexicalTypeaheadMenuPlugin PR
  3. Removing the deprecated itemValue prop which was throwing the above warning
  4. Resorting to loading={undefined} when the prop is not set, and true otherwise so it's not passed to a DOM element (ul or div by default)

This solved all the warnings in my limited testing. I've also tested it this in a stripped down version of the Next.js www package (in React 18), and removing all props but items from the <BeautifulMentionsPlugin> threw the same loading DOM warning as above, implying the warning was not related to React 18 but an actual bug.

The reason this is flagged as a feature-request and not a direct PR is that some of my tests were failing.

I hope I was able to give some background to get this going, but I think if this project truly does not need React 18 features, it would be nice to open the gates for more users who could benefit from it. At the very least it would be more transparent of its actual requirements.

Type custom-beautifulMention in node CustomBeautifulMentionNode does not match registered node

Describe the bug
I am getting the following error when adding a mention with the createBeautifulMentionNode

Create node: Type custom-beautifulMention in node CustomBeautifulMentionNode does not match registered node CustomBeautifulMentionNode with the same type

To Reproduce
Steps to reproduce the behavior:

  1. Create the custom node:
export const CustomMentionComponent = forwardRef<
	HTMLDivElement,
	BeautifulMentionComponentProps
>(({ trigger, value, children, ...other }, ref) => {
	return (
		<div {...other} ref={ref} title={trigger + value}>
	        prefix {value} suffix
		</div>
	);
});
  1. Add a node in the config
		nodes: [...createBeautifulMentionNode(CustomMentionComponent)],

Expected behavior
On the editor try to add a mention

Screenshots
Screenshot 2023-11-12 at 20 44 25

Desktop (please complete the following information):

  • Chrome Version 119.0.6045.123 (Official Build) (arm64) on MacOS 13.5

Getting type error on ...createBeautifulMentionNode on lexical latest 0.12.5

I started getting the following type error after upgrading Lexical:
here:
...createBeautifulMentionNode(CustomMentionComponent)

Type '{ new (trigger: string, value: string, data?: { [p: string]: BeautifulMentionsItemData; } | undefined, key?: string | undefined): { [x: string]: any; exportJSON(): SerializedBeautifulMentionNode; ... 54 more ...; markDirty(): void; }; ... 4 more ...; transform(): ((node: LexicalNode) => void) | null; } | { ...; }' is not assignable to type 'Klass<LexicalNode> | LexicalNodeReplacement'.
  Type '{ new (trigger: string, value: string, data?: { [p: string]: BeautifulMentionsItemData; } | undefined, key?: string | undefined): { [x: string]: any; exportJSON(): SerializedBeautifulMentionNode; ... 54 more ...; markDirty(): void; }; ... 4 more ...; transform(): ((node: LexicalNode) => void) | null; }' is not assignable to type 'Klass<LexicalNode> | LexicalNodeReplacement'.
    Type '{ new (trigger: string, value: string, data?: { [p: string]: BeautifulMentionsItemData; } | undefined, key?: string | undefined): { [x: string]: any; exportJSON(): SerializedBeautifulMentionNode; ... 54 more ...; markDirty(): void; }; ... 4 more ...; transform(): ((node: LexicalNode) => void) | null; }' is not assignable to type 'Klass<LexicalNode>'.
      Type '{ new (trigger: string, value: string, data?: { [p: string]: BeautifulMentionsItemData; } | undefined, key?: string | undefined): { [x: string]: any; exportJSON(): SerializedBeautifulMentionNode; ... 54 more ...; markDirty(): void; }; ... 4 more ...; transform(): ((node: LexicalNode) => void) | null; }' is not assignable to type 'new (...args: any[]) => LexicalNode'.
        Type '{ [x: string]: any; exportJSON(): SerializedBeautifulMentionNode; component(): ElementType<BeautifulMentionComponentProps<{}>> | null; ... 53 more ...; markDirty(): void; }' is missing the following properties from type 'LexicalNode': selectStart, selectEndts(2322)

The error message indicates that the return type of createBeautifulMentionNode(CustomMentionComponent) is not assignable to the type Klass | LexicalNodeReplacement. This means that the object returned by createBeautifulMentionNode(CustomMentionComponent) does not match the expected type.

`$` cannot be used as trigger character

Describe the bug
It doesn't work when $ is used as a trigger character. It seems it's caused by $ being a special character (end of line) in regex.
I am trying to use it as a stock symbol. $ is widely used as a stock symbol on Twitter.

To Reproduce

  1. use '$' as a trigger character

Expected behavior
It should work normally accepting $ as a trigger character.

Screenshots

Desktop (please complete the following information):

  • OS: mac,windows
  • Browser: chrome, safari
  • Version: latest

Smartphone (please complete the following information):

  • Device: any
  • OS: any
  • Browser: any
  • Version: latest

Additional context

"(?:" + triggers.join("|") + ")";

maybe surround trigger characters with [ and ] or escape with \?

Conditional classes on mention chip

Apologies if this is not the correct place to ask this, but I was hoping to get some guidance.

Basically, I want to be able to assign classnames to mention chips that have nothing to do with the trigger. Is this possible with the current feature set?

Something like this, where the class could perhaps come from the mentionItems objects:

<span class="danger" data-beautiful-mention="#Email">#Email</span>
<span class="urgent" data-beautiful-mention="#Name">#Name</span>
<span class="someOtherClass" data-beautiful-mention="#Due">#Due</span>

Mention with initial Text for Edit Text Cases

Is there a way to apply mention from a text that returned from the editor?
I try to implement mention on a dialog, when dialog is closed, the text is passed to other component.
I have edit button and when I click it, I pass it back to editor and set using editorConfig initText.

const editorConfig = {
 namespace: "name",
 onError(error: any) {
  throw error;
 },
  nodes: [BeautifulMentionNode],
  editorState: () => {
      const root = $getRoot();
      root.clear();
      const p = $createParagraphNode();
      p.append($createTextNode(initText));
      root.append(p);
    }
  };

How can I apply it? because its just a plain text in editor. thanks

TypeScript Error with `$nodesOfType` and Custom `BeautifulMentionNode`

Describe the bug
In the lexical-beautiful-mentions plugin, using $nodesOfType with a custom BeautifulMentionNode is causing a TypeScript error.

image Screenshot 2024-01-11 at 7 33 12โ€ฏAM (But extracting mentions from the root works well for me this way)

To Reproduce
Steps to reproduce the behavior:

  1. Define a custom BeautifulMentionNode.
  2. Use $nodesOfType with the custom BeautifulMentionNode.
  3. Notice the TypeScript error.

Argument of type '{ new (trigger: string, value: string, data?: { [p: string]: BeautifulMentionsItemData; } | undefined, key?: string | undefined): { exportJSON(): SerializedBeautifulMentionNode; ... 57 more ...; markDirty(): void; }; ... 4 more ...; transform(): ((node: LexicalNode) => void) | null; } | { ...; }' is not assignable to parameter of type 'GenericConstructor<{ exportJSON(): SerializedBeautifulMentionNode; component(): ElementType<BeautifulMentionComponentProps<{}>> | null; ... 56 more ...; markDirty(): void; }> & GenericConstructor<...> & { ...; }'.
Type '{ replace: typeof BeautifulMentionNode; with: (node: BeautifulMentionNode) => { exportJSON(): SerializedBeautifulMentionNode; ... 57 more ...; markDirty(): void; }; }' is not assignable to type 'GenericConstructor<{ exportJSON(): SerializedBeautifulMentionNode; component(): ElementType<BeautifulMentionComponentProps<{}>> | null; ... 56 more ...; markDirty(): void; }> & GenericConstructor<...> & { ...; }'.
Type '{ replace: typeof BeautifulMentionNode; with: (node: BeautifulMentionNode) => { exportJSON(): SerializedBeautifulMentionNode; ... 57 more ...; markDirty(): void; }; }' is not assignable to type 'GenericConstructor<{ exportJSON(): SerializedBeautifulMentionNode; component(): ElementType<BeautifulMentionComponentProps<{}>> | null; ... 56 more ...; markDirty(): void; }>'.
Type '{ replace: typeof BeautifulMentionNode; with: (node: BeautifulMentionNode) => { exportJSON(): SerializedBeautifulMentionNode; ... 57 more ...; markDirty(): void; }; }' provides no match for the signature 'new (...args: any[]): { exportJSON(): SerializedBeautifulMentionNode; component(): ElementType<BeautifulMentionComponentProps<{}>> | null; ... 56 more ...; markDirty(): void; }'.ts(2345)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.