Skip to content

Commit

Permalink
User cards for Mentions plugin (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
DiggesT authored Jun 1, 2023
1 parent 7d3f42f commit fd2fcf4
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 10 deletions.
50 changes: 46 additions & 4 deletions src/nodes/MentionNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,26 @@ export type SerializedMentionNode = Spread<
typeof SerializedTextNode
>;

type PopoverCard = {
card: HTMLElement;
leftOffset: number;
topOffset: number;
};

const mentionStyle = 'background-color: rgba(24, 119, 232, 0.2)';

export class MentionNode extends TextNode {
__mention: string;
__popoverCard: PopoverCard;

static getType(): string {
return 'mention';
}

static clone(node: MentionNode): MentionNode {
return new MentionNode(node.__mention, node.__text, node.__key);
return new MentionNode(node.__mention, undefined, node.__text, node.__key);
}

static importJSON(serializedNode: SerializedMentionNode): MentionNode {
const node = $createMentionNode(serializedNode.mentionName);
node.setTextContent(serializedNode.text);
Expand All @@ -43,9 +52,20 @@ export class MentionNode extends TextNode {
return node;
}

constructor(mentionName: string, text?: string, key?: NodeKey) {
constructor(
mentionName: string,
popover?: PopoverCard,
text?: string,
key?: NodeKey
) {
super(text ?? mentionName, key);
this.__mention = mentionName;
if (popover !== undefined) {
this.__popoverCard = popover;
this.__popoverCard.card.id = 'verbum-mention-popover';
this.__popoverCard.card.style.position = 'absolute';
this.removePopover();
}
}

exportJSON(): SerializedMentionNode {
Expand All @@ -57,10 +77,29 @@ export class MentionNode extends TextNode {
};
}

removePopover = () => {
const existingPopover = document.getElementById(this.__popoverCard.card.id);
if (existingPopover && existingPopover.parentElement) {
existingPopover.parentElement.removeChild(existingPopover);
}
};

createDOM(config: EditorConfig): HTMLElement {
const dom = super.createDOM(config);
dom.style.cssText = mentionStyle;
dom.className = 'mention';

dom.addEventListener('pointerover', (event) => {
const { left, top } = dom.getBoundingClientRect();
this.__popoverCard.card.style.left = `${left - this.__popoverCard.leftOffset}px`;
this.__popoverCard.card.style.top = `${top - this.__popoverCard.topOffset}px`;
document.body.appendChild(this.__popoverCard.card);
});

dom.addEventListener('pointerout', (event) => {
this.removePopover();
});

return dom;
}

Expand All @@ -69,8 +108,11 @@ export class MentionNode extends TextNode {
}
}

export function $createMentionNode(mentionName: string): MentionNode {
const mentionNode = new MentionNode(mentionName);
export function $createMentionNode(
mentionName: string,
popover?: PopoverCard
): MentionNode {
const mentionNode = new MentionNode(mentionName, popover);
mentionNode.setMode('segmented').toggleDirectionless();
return mentionNode;
}
Expand Down
54 changes: 48 additions & 6 deletions src/plugins/MentionsPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,38 @@ import {
MenuOption,
useBasicTypeaheadTriggerMatch,
} from '@lexical/react/LexicalTypeaheadMenuPlugin';
import { $createTextNode, TextNode } from 'lexical';
import { TextNode } from 'lexical';
import { useCallback, useEffect, useMemo, useState } from 'react';
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import { renderToStaticMarkup } from 'react-dom/server';
import { $createMentionNode } from '../nodes/MentionNode';
import { $createAutoLinkNode } from '@lexical/link';

import './MentionsPlugin.css';

type SearchData<A> = (p: string) => Promise<A[]>;

type OffsetCard = {
leftOffset: number;
topOffset: number;
};

type PopoverCard<A> = {
card: (data: A) => JSX.Element;
offset: OffsetCard;
};

type UserCard = {
card: JSX.Element;
offset: OffsetCard;
};

type GetTypeaheadValues<A> = (result: A) => {
url: string;
value: string;
picture: JSX.Element;
popoverCard?: PopoverCard<A>;
};

const PUNCTUATION =
Expand Down Expand Up @@ -181,12 +197,19 @@ class MentionMenuOption extends MenuOption {
name: string;
picture: JSX.Element;
url: string;

constructor(name: string, picture: JSX.Element, url?: string) {
userCard: UserCard;

constructor(
name: string,
picture: JSX.Element,
url?: string,
userCard?: UserCard
) {
super(name);
this.name = name;
this.picture = picture;
this.url = url;
this.userCard = userCard;
}
}

Expand Down Expand Up @@ -249,7 +272,16 @@ export default function MentionsPlugin<A>(props: {
new MentionMenuOption(
getTypeaheadValues(result).value,
getTypeaheadValues(result).picture,
getTypeaheadValues(result).url
getTypeaheadValues(result).url,
{
card: getTypeaheadValues(result).popoverCard.card(result),
offset: {
leftOffset:
getTypeaheadValues(result).popoverCard.offset.leftOffset,
topOffset:
getTypeaheadValues(result).popoverCard.offset.topOffset,
},
}
)
)
.slice(0, SUGGESTION_LIST_LENGTH_LIMIT),
Expand All @@ -264,7 +296,17 @@ export default function MentionsPlugin<A>(props: {
) => {
editor.update(() => {
if (nodeToReplace) {
const mentionNode = $createMentionNode(`@${selectedOption.name}`);
const popover = document.createElement('div');
const staticElement = renderToStaticMarkup(
selectedOption.userCard.card
);
popover.innerHTML = staticElement;

const mentionNode = $createMentionNode(`@${selectedOption.name}`, {
card: popover,
leftOffset: selectedOption.userCard.offset.leftOffset,
topOffset: selectedOption.userCard.offset.topOffset,
});
const linkNode = $createAutoLinkNode(selectedOption.url);
linkNode.append(mentionNode);
nodeToReplace.replace(linkNode);
Expand Down

0 comments on commit fd2fcf4

Please sign in to comment.