Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat mentions #296

Merged
merged 23 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7597bd1
⚡️ chore(package.json): update @tiptap/extension-highlight and @tipta…
marcbon Jan 10, 2024
60fadc2
✨ feat(icons): add new SVG icons for text alignment and formatting
marcbon Jan 10, 2024
a64d0a4
✨ feat(bar.tsx): add CommentBar component for chat message formatting…
marcbon Jan 10, 2024
cca3c3e
🚀 feat(chat/index.tsx): add CommentBar component to Chat component
marcbon Jan 10, 2024
3db4cc9
✨ feat(chat): add Chat.Menu component to ChatPanel
marcbon Jan 10, 2024
8b1dc9e
WIP active class¨
iDome89 Jan 11, 2024
98c0463
✨ feat(chat/_types.tsx): add support for hasButtonsMenu property in C…
marcbon Jan 11, 2024
ecf9ab4
🔨 refactor(bar.tsx): remove unused import statement
marcbon Jan 11, 2024
7fc5fda
🔥 refactor(chat/index.tsx): remove unused CommentBar component import…
marcbon Jan 11, 2024
651eb14
🔥 refactor(chat/index.stories.tsx): remove unused Chat.Menu component
marcbon Jan 11, 2024
e75038f
wip(chat-mentions): add types and components for mentions functionality
cannarocks Jan 8, 2024
a63e89e
wip(mention): style tag
cannarocks Jan 12, 2024
dd01e47
feat(mentions): reuse chatEditor in both comments and commentsBox
cannarocks Jan 12, 2024
d7f0040
feat(chat): add usersQuery to context
cannarocks Jan 17, 2024
93a8b60
🐛 fix(chatContext.tsx): add setMentionableUsers to dependencies array…
marcbon Jan 18, 2024
4003334
refactor(chat): Remove unused state variables from ChatContext
cannarocks Jan 18, 2024
e3394c5
chore(tiptap): Update @tiptap packages to version 2.1.16
cannarocks Jan 18, 2024
aa7c690
refactor(chat): use directly editor object in order to fix sync issues
cannarocks Jan 19, 2024
144fb56
feat(editor-button): Add at-fill.svg icon and update mention icon in …
cannarocks Jan 19, 2024
0590ca8
feat: added shortcut for to send message to chat
iDome89 Jan 19, 2024
cbb8fef
🔥 chore(icons): remove unused icons
marcbon Jan 19, 2024
0f3cf60
🔧 fix(mentionList.tsx): fix styling issue with selected items in Ment…
marcbon Jan 19, 2024
5dafa4a
Merge pull request #295 from AppQuality/add-suggestions-to-editor
marcbon Jan 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@
"@nivo/sunburst": "^0.80.0",
"@nivo/tooltip": "^0.80.0",
"@nivo/waffle": "^0.80.0",
"@tiptap/extension-bubble-menu": "^2.0.0-beta.61",
"@tiptap/extension-character-count": "^2.0.0-beta.31",
"@tiptap/extension-link": "^2.0.4",
"@tiptap/extension-placeholder": "^2.0.0-beta.53",
"@tiptap/extension-typography": "^2.0.0-beta.22",
"@tiptap/pm": "^2.0.3",
"@tiptap/react": "^2.0.0-beta.114",
"@tiptap/starter-kit": "^2.0.0-beta.191",
"@tiptap/extension-bubble-menu": "2.1.16",
"@tiptap/extension-character-count": "2.1.16",
"@tiptap/extension-link": "2.1.16",
"@tiptap/extension-mention": "2.1.16",
"@tiptap/extension-placeholder": "2.1.16",
"@tiptap/extension-typography": "2.1.16",
"@tiptap/pm": "2.1.16",
"@tiptap/react": "2.1.16",
"@tiptap/starter-kit": "2.1.16",
"@tiptap/suggestion": "2.1.16",
"@types/react-slick": "^0.23.10",
"@zendeskgarden/css-bedrock": "^9.0.0",
"@zendeskgarden/react-accordions": "^8.49.0",
Expand All @@ -63,6 +65,7 @@
"@zendeskgarden/react-typography": "^8.49.0",
"react-slick": "^0.29.0",
"react-window": "^1.8.6",
"tippy.js": "^6.3.7",
"ua-parser-js": "^1.0.2"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/at-fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/stories/chat/_types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ import { BubbleMenuProps, EditorOptions } from "@tiptap/react";

type validationStatus = "success" | "warning" | "error";

export type SuggestedUser = { id: number; name: string; };

export interface ChatEditorArgs extends Partial<EditorOptions> {
placeholderOptions?: Partial<PlaceholderOptions>;
hasInlineMenu?: boolean;
hasButtonsMenu?: boolean;
bubbleOptions?: any;
author: Author;
i18n?: {
menu?: {
bold?: string;
italic?: string;
mention?: string;
};
};
}

export interface Author {
Expand Down
38 changes: 25 additions & 13 deletions src/stories/chat/context/chatContext.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
import { Editor } from "@tiptap/react";
import React, { createContext, useContext, useMemo, useState } from "react";
import { SuggestedUser } from "../_types";

export type ChatContextType = {
isEditing: boolean;
setIsEditing: (isEditing: boolean) => void;
comment: string;
setComment: (comment: string) => void;
triggerSave: () => void;
editor?: Editor;
setEditor: React.Dispatch<React.SetStateAction<Editor | undefined>>;
mentionableUsers: (props: { query: string }) => Promise<SuggestedUser[]>;
};

export const ChatContext = createContext<ChatContextType | null>(null);

export const ChatContextProvider = ({
onSave,
setMentionableUsers,
children,
}: {
onSave?: (editor: Editor) => void;
onSave?: (editor: Editor, mentions: SuggestedUser[]) => void;
children: React.ReactNode;
setMentionableUsers: (props: { query: string }) => Promise<SuggestedUser[]>;
}) => {
const [isEditing, setIsEditing] = useState<boolean>(false);
const [comment, setComment] = useState<string>("");
const [editor, setEditor] = useState<Editor | undefined>();

const getMentions = (editor: Editor) => {
const result: SuggestedUser[] = [];

editor.state.doc.descendants((node) => {
if (node.type.name === "mention") {
// Add only if it's not already in the array
if (!result.some((r) => r.id === node.attrs.id))
result.push({
id: node.attrs.id,
name: node.attrs.name
});
}
});

return result;
};

const chatContextValue = useMemo(
() => ({
isEditing,
setIsEditing,
comment,
setComment,
editor,
setEditor,
triggerSave: () => {
if (editor && onSave) {
onSave(editor);
onSave(editor, getMentions(editor));
editor.commands.clearContent();
}
},
mentionableUsers: setMentionableUsers,
}),
[comment, setComment, isEditing, setIsEditing, editor, setEditor, onSave]
[editor, setEditor, onSave, setMentionableUsers]
);

return (
Expand Down
116 changes: 86 additions & 30 deletions src/stories/chat/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Meta, StoryFn } from "@storybook/react";
import { PlaceholderOptions } from "@tiptap/extension-placeholder";
import { Editor as TipTapEditor } from "@tiptap/react";
import styled from "styled-components";
import { Chat, ChatProvider, useChatContext } from ".";
import { Button } from "../buttons/button";
import { Col } from "../grid/col";
import { Grid } from "../grid/grid";
import { Row } from "../grid/row";
import { ChatEditorArgs } from "./_types";
import { ChatEditorArgs, SuggestedUser } from "./_types";
import { Comment } from "./parts/comment";

const ButtonsContainer = styled.div`
padding: 0px 16px;
display: flex;
`;
interface EditorStoryArgs extends ChatEditorArgs {
children?: any;
comments?: {
Expand All @@ -21,37 +26,94 @@ interface EditorStoryArgs extends ChatEditorArgs {
}[];
editorText?: string;
background?: string;
onSave: (editor: TipTapEditor) => void;
onSave: (editor: TipTapEditor, mentions: SuggestedUser[]) => void;
placeholderOptions?: Partial<PlaceholderOptions>;
}

const ChatPanel = ({ background, ...args }: EditorStoryArgs) => {
const { triggerSave } = useChatContext();
const { editor, triggerSave } = useChatContext();
return (
<Chat>
<Chat.Header>Titolone</Chat.Header>
<Chat.Comments chatBkg={background}>
{args.comments?.map((comment, index) => (
{args.comments?.map((comment) => (
<Comment {...comment}>
<>altre cose</>
</Comment>
))}
</Chat.Comments>
<Chat.Input {...args}>{args.editorText}</Chat.Input>
<Chat.Footer>
<Button isBasic>Cancel</Button>
<Button onClick={triggerSave}>Save</Button>
<Chat.Footer showShortcut>
<ButtonsContainer>
<Button isBasic onClick={() => editor?.commands.clearContent()}>
Cancel
</Button>
<Button onClick={triggerSave}>Save</Button>
</ButtonsContainer>
</Chat.Footer>
</Chat>
);
};

const Template: StoryFn<EditorStoryArgs> = ({ children, ...args }) => {
const getUsers = async ({ query }: { query: string }) => {
return [
{
id: 1,
name: "John Doe",
avatar: "https://i.pravatar.cc/150?img=1",
},
{
id: 2,
name: "Jane Doe",
avatar: "https://i.pravatar.cc/150?img=2",
},
{
id: 3,
name: "John Smith",
avatar: "https://i.pravatar.cc/150?img=3",
},
{
id: 4,
name: "Jane Smith",
avatar: "https://i.pravatar.cc/150?img=4",
},
{
id: 5,
name: "Pippo Baudo",
avatar: "https://i.pravatar.cc/150?img=5",
},
{
id: 6,
name: "Pippo Franco",
avatar: "https://i.pravatar.cc/150?img=6",
},
{
id: 7,
name: "Pippo Inzaghi",
avatar: "https://i.pravatar.cc/150?img=7",
},
{
id: 8,
name: "Pippo Civati",
avatar: "https://i.pravatar.cc/150?img=8",
},
{
id: 9,
name: "Pippo Delbono",
avatar: "https://i.pravatar.cc/150?img=9",
},
].filter((item) => {
if (!query) return item;
return item.name.toLowerCase().startsWith(query.toLowerCase());
});
};

return (
<Grid>
<Row>
<Col xs={12} sm={6}>
<ChatProvider onSave={args.onSave}>
<Col xs={12} sm={8} md={6}>
<ChatProvider setMentionableUsers={getUsers} onSave={args.onSave}>
<ChatPanel {...args} />
</ChatProvider>
</Col>
Expand All @@ -63,8 +125,9 @@ const Template: StoryFn<EditorStoryArgs> = ({ children, ...args }) => {
const defaultArgs: EditorStoryArgs = {
children:
"<p>I'm <em>a</em> <strong>stupid</strong> <code>editor</code>!</p>",
onSave: (editor: TipTapEditor) => {
onSave: (editor: TipTapEditor, mentions) => {
console.log("we have to save this", editor.getHTML());
console.log("mentions", mentions);
},
author: {
avatar: "LC",
Expand All @@ -85,31 +148,16 @@ const defaultArgs: EditorStoryArgs = {
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong>",
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
avatar: "MB",
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong>",
message:
"Hi, I'm a comment too but with <strong>bold</strong>. lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl",
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
avatar: "MB",
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong>. lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec auctor, nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl nisl eget ultricies ultricies, nunc nisl ultricies nunc, quis ultricies nisl",
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
avatar: "MB",
},
},
{
message: "Hi, I'm a comment too but with <strong>bold</strong> askdlhfksadhjfkljafshbcfkjsdhbkjdhfksjdfhabfshdbkfvhksdajhfbvhldsjfvdjshflkvdsbjhfjkvskfhbvasjhfksjbfvsdbvkjshvbkfasjhvfksjhfbkfbvksjhjvfshjvbsdhvdbvskjsdbhfkvsjbfjkvbsdfhwrap",
message: `Hi <mention data-type="mention" data-mention-id="1" data-mention-name="John Doe">@John Doe</mention>, I'm a comment too but with <strong>bold</strong> askdlhfksadhjfkljafshbcfkjsdhbkjdhfksjdfhabfshdbkfvhksdajhfbvhldsjfvdjshflkvdsbjhfjkvskfhbvasjhfksjbfvsdbvkjshvbkfasjhvfksjhfbkfbvksjhjvfshjvbsdhvdbvskjsdbhfkvsjbfjkvbsdfhwrap`,
date: " | 27 dic. 2023 | 12:00",
author: {
name: "Marco B.",
Expand Down Expand Up @@ -143,10 +191,18 @@ Placeholder.args = {
},
};

export const BubbleMenu = Template.bind({});
BubbleMenu.args = {
export const Menus = Template.bind({});
Menus.args = {
...defaultArgs,
hasInlineMenu: true,
hasButtonsMenu: true,
i18n: {
menu: {
bold: "Grassetto",
italic: "Corsivo",
mention: "Menziona",
},
},
};

export const CustomBackground = Template.bind({});
Expand Down
62 changes: 62 additions & 0 deletions src/stories/chat/parts/bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import styled from "styled-components";
import { Tooltip } from "../../tooltip";
import { ChatEditorArgs } from "../_types";
import { Editor } from "@tiptap/react";
import { EditorButton } from "./editorButton";

const MenuContainer = styled.div`
padding: ${({ theme }) => theme.space.xxs} 0;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
`;

const VerticalDivider = styled.div`
width: 2px;
height: 24px;
background-color: ${({ theme }) => theme.palette.grey[300]};
margin: 0 ${({ theme }) => theme.space.xs};
`;

const CommentBar = ({
editor,
i18n,
}: Partial<ChatEditorArgs> & {
editor?: Editor;
}) => {

if(!editor) return null;

return (
<MenuContainer>
<Tooltip
content={i18n?.menu?.bold ?? "Bold text"}
placement="top"
type="light"
size="small"
>
<EditorButton editor={editor} type="bold" />
</Tooltip>
<Tooltip
content={i18n?.menu?.italic ?? "Italic text"}
placement="top"
type="light"
size="small"
>
<EditorButton editor={editor} type="italic" />
</Tooltip>
<VerticalDivider />
<Tooltip
content={i18n?.menu?.mention ?? "Add a mention"}
placement="top"
type="light"
size="small"
>
<EditorButton editor={editor} type="mention" />
</Tooltip>
</MenuContainer>
);
};

export { CommentBar };
Loading
Loading