Files
outline/app/editor/components/MentionMenu.tsx
Apoorv Mishra de031b365c Capability to mention users in a document (#4838)
* feat: mention user

* fix: trigger api call on every letter typed

* fix: this allows command menu to re-render upon props change, shouldComponentUpdate prevented re-rendering when necessary

* fix: add node

* fix: mention node styling

* fix: Caret not visible after inserting mention

* fix: apply mentionRule

* fix: label is to be obtained from content, not attrs

* feat: add mentions table and model

* fix: typo

* fix: make all mention nodes visible in shared doc

* feat: parse mention ids from doc text

* feat: MentionsProcessor

* feat: documents.publish tests

* feat: tests for MentionsProcessor

* feat: schedule notifs for mentions

* fix: get rid of Mention model

* fix: put actor id and mention id in raw md

* Revert "fix: put actor id and mention id in raw md"

This reverts commit 3bb8a22e3c560971dccad6d2f82266256bcb2d96.

* Revert "Revert "fix: put actor id and mention id in raw md""

This reverts commit 3c5b36c40cebf147663908cf27d0dce6488adfad.

* fix: review

* fix: no need of set

* fix: show avatar

* fix: get rid of eventName

* fix: font-weight

* fix: prioritize mention notifs

* fix: store id in md

* fix: no need of prepending m

* fix: fetchPage

* fix: Avatars incorrect color

* fix: remove scanRE

* fix: test

* fix: include alphabet other than latin

* lockfile

* fix: regex should test for letters, marks and digits

---------

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-03-06 14:54:57 -08:00

114 lines
2.8 KiB
TypeScript

import * as React from "react";
import { useTranslation } from "react-i18next";
import { v4 } from "uuid";
import { MenuItem } from "@shared/editor/types";
import { MentionType } from "@shared/types";
import User from "~/models/User";
import Avatar from "~/components/Avatar";
import Flex from "~/components/Flex";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import CommandMenu, { Props } from "./CommandMenu";
import MentionMenuItem from "./MentionMenuItem";
interface MentionItem extends MenuItem {
name: string;
user: User;
appendSpace: boolean;
attrs: {
id: string;
type: MentionType;
modelId: string;
label: string;
actorId?: string;
};
}
type MentionMenuProps = Omit<
Props<MentionItem>,
"renderMenuItem" | "items" | "onLinkToolbarOpen" | "embeds" | "onClearSearch"
>;
function MentionMenu({ search, ...rest }: MentionMenuProps) {
const [items, setItems] = React.useState<MentionItem[]>([]);
const { t } = useTranslation();
const { users, auth } = useStores();
const { data, request } = useRequest(
React.useCallback(() => users.fetchPage({ query: search }), [users, search])
);
React.useEffect(() => {
request();
}, [request]);
React.useEffect(() => {
if (data) {
setItems(
data.map((user) => ({
name: "mention",
user,
title: user.name,
appendSpace: true,
attrs: {
id: v4(),
type: MentionType.User,
modelId: user.id,
actorId: auth.user?.id,
label: user.name,
},
}))
);
}
}, [auth.user?.id, data]);
const clearSearch = () => {
const { state, dispatch } = rest.view;
// clear search input
dispatch(
state.tr.insertText(
"",
state.selection.$from.pos - (search ?? "").length - 1,
state.selection.to
)
);
};
const containerId = "mention-menu-container";
return (
<CommandMenu
{...rest}
id={containerId}
filterable={false}
onClearSearch={clearSearch}
search={search}
renderMenuItem={(item, _index, options) => (
<MentionMenuItem
onClick={options.onClick}
selected={options.selected}
title={item.title}
label={item.attrs.label}
containerId={containerId}
icon={
<Flex
align="center"
justify="center"
style={{ width: 24, height: 24 }}
>
<Avatar
model={item.user}
showBorder={false}
alt={t("Profile picture")}
size={16}
/>
</Flex>
}
/>
)}
items={items}
/>
);
}
export default MentionMenu;