diff --git a/package.json b/package.json index 242594cf..5065d8c5 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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": { diff --git a/src/assets/icons/at-fill.svg b/src/assets/icons/at-fill.svg new file mode 100755 index 00000000..4f609105 --- /dev/null +++ b/src/assets/icons/at-fill.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/stories/chat/_types.tsx b/src/stories/chat/_types.tsx index 01b3937e..a099859d 100644 --- a/src/stories/chat/_types.tsx +++ b/src/stories/chat/_types.tsx @@ -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 { placeholderOptions?: Partial; hasInlineMenu?: boolean; + hasButtonsMenu?: boolean; bubbleOptions?: any; author: Author; + i18n?: { + menu?: { + bold?: string; + italic?: string; + mention?: string; + }; + }; } export interface Author { diff --git a/src/stories/chat/context/chatContext.tsx b/src/stories/chat/context/chatContext.tsx index 5a2e11ef..257f6552 100644 --- a/src/stories/chat/context/chatContext.tsx +++ b/src/stories/chat/context/chatContext.tsx @@ -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>; + mentionableUsers: (props: { query: string }) => Promise; }; export const ChatContext = createContext(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; }) => { - const [isEditing, setIsEditing] = useState(false); - const [comment, setComment] = useState(""); const [editor, setEditor] = useState(); + 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 ( diff --git a/src/stories/chat/index.stories.tsx b/src/stories/chat/index.stories.tsx index 788f4ae3..0978821f 100644 --- a/src/stories/chat/index.stories.tsx +++ b/src/stories/chat/index.stories.tsx @@ -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?: { @@ -21,37 +26,94 @@ interface EditorStoryArgs extends ChatEditorArgs { }[]; editorText?: string; background?: string; - onSave: (editor: TipTapEditor) => void; + onSave: (editor: TipTapEditor, mentions: SuggestedUser[]) => void; placeholderOptions?: Partial; } const ChatPanel = ({ background, ...args }: EditorStoryArgs) => { - const { triggerSave } = useChatContext(); + const { editor, triggerSave } = useChatContext(); return ( Titolone - {args.comments?.map((comment, index) => ( + {args.comments?.map((comment) => ( <>altre cose ))} {args.editorText} - - - + + + + + ); }; const Template: StoryFn = ({ 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 ( - - + + @@ -63,8 +125,9 @@ const Template: StoryFn = ({ children, ...args }) => { const defaultArgs: EditorStoryArgs = { children: "

I'm a stupid editor!

", - onSave: (editor: TipTapEditor) => { + onSave: (editor: TipTapEditor, mentions) => { console.log("we have to save this", editor.getHTML()); + console.log("mentions", mentions); }, author: { avatar: "LC", @@ -85,15 +148,8 @@ const defaultArgs: EditorStoryArgs = { }, }, { - message: "Hi, I'm a comment too but with bold", - date: " | 27 dic. 2023 | 12:00", - author: { - name: "Marco B.", - avatar: "MB", - }, - }, - { - message: "Hi, I'm a comment too but with bold", + message: + "Hi, I'm a comment too but with bold. 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.", @@ -101,15 +157,7 @@ const defaultArgs: EditorStoryArgs = { }, }, { - message: "Hi, I'm a comment too but with bold. 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 bold askdlhfksadhjfkljafshbcfkjsdhbkjdhfksjdfhabfshdbkfvhksdajhfbvhldsjfvdjshflkvdsbjhfjkvskfhbvasjhfksjbfvsdbvkjshvbkfasjhvfksjhfbkfbvksjhjvfshjvbsdhvdbvskjsdbhfkvsjbfjkvbsdfhwrap", + message: `Hi @John Doe, I'm a comment too but with bold askdlhfksadhjfkljafshbcfkjsdhbkjdhfksjdfhabfshdbkfvhksdajhfbvhldsjfvdjshflkvdsbjhfjkvskfhbvasjhfksjbfvsdbvkjshvbkfasjhvfksjhfbkfbvksjhjvfshjvbsdhvdbvskjsdbhfkvsjbfjkvbsdfhwrap`, date: " | 27 dic. 2023 | 12:00", author: { name: "Marco B.", @@ -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({}); diff --git a/src/stories/chat/parts/bar.tsx b/src/stories/chat/parts/bar.tsx new file mode 100644 index 00000000..1832a1a8 --- /dev/null +++ b/src/stories/chat/parts/bar.tsx @@ -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 & { + editor?: Editor; +}) => { + + if(!editor) return null; + + return ( + + + + + + + + + + + + + ); +}; + +export { CommentBar }; diff --git a/src/stories/chat/parts/comment.tsx b/src/stories/chat/parts/comment.tsx index 33751b7e..f6e66b1b 100644 --- a/src/stories/chat/parts/comment.tsx +++ b/src/stories/chat/parts/comment.tsx @@ -2,9 +2,12 @@ import { PropsWithChildren } from "react"; import { Title } from "../../title"; import { Card } from "../../cards"; import { styled } from "styled-components"; -import { Editor } from "../../editor"; import { Author } from "../_types"; import { Avatar } from "../../avatar"; +import { useChatContext } from "../context/chatContext"; +import { Content, useEditor, EditorContent } from "@tiptap/react"; +import { editorExtensions } from "./extensions"; +import { EditorContainer } from "./containers"; const CommentCard = styled(Card)` padding: ${({ theme }) => `${theme.space.base * 3}px ${theme.space.sm}`}; @@ -43,8 +46,8 @@ const Footer = styled.div` gap: ${({ theme }) => theme.space.xs}; `; const CommentTitle = styled(Title)` -color: ${({ theme }) => theme.palette.blue[600]}; -` + color: ${({ theme }) => theme.palette.blue[600]}; +`; export const Comment = ({ author, @@ -52,6 +55,17 @@ export const Comment = ({ children, date, }: PropsWithChildren<{ author: Author; message: string; date: string }>) => { + const { mentionableUsers } = useChatContext(); + + const ext = editorExtensions({ mentionableUsers }); + + const ed = useEditor({ + extensions: ext, + content: (message as Content) || "", + }); + + if (!ed) return null; + return ( @@ -67,7 +81,9 @@ export const Comment = ({ {date} - {message} + + + diff --git a/src/stories/chat/parts/commentBox.tsx b/src/stories/chat/parts/commentBox.tsx index 6c98922b..7117b2e4 100644 --- a/src/stories/chat/parts/commentBox.tsx +++ b/src/stories/chat/parts/commentBox.tsx @@ -1,24 +1,18 @@ +import styled from "styled-components"; import { useEditor, EditorContent, Editor as TipTapEditor, Content, } from "@tiptap/react"; -import styled from "styled-components"; - -import Typography from "@tiptap/extension-typography"; -import Link from "@tiptap/extension-link"; -import StarterKit from "@tiptap/starter-kit"; -import Placeholder from "@tiptap/extension-placeholder"; -import CharacterCount from "@tiptap/extension-character-count"; - -import { editorStyle } from "../../shared/editorStyle"; -import { ChatArgs, ChatEditorArgs } from "../_types"; +import { ChatEditorArgs } from "../_types"; import { KeyboardEvent as ReactKeyboardEvent, PropsWithChildren } from "react"; + import { FloatingMenu } from "../../editor/floatingMenu"; -import { FauxInput } from "@zendeskgarden/react-forms"; -import { Avatar } from "../../avatar"; import { useChatContext } from "../context/chatContext"; +import { CommentBar } from "./bar"; +import { editorExtensions } from "./extensions"; +import { EditorContainer } from "./containers"; const ChatBoxContainer = styled.div` display: flex; @@ -27,20 +21,6 @@ const ChatBoxContainer = styled.div` padding: ${({ theme }) => `${theme.space.base * 4}px ${theme.space.sm} 0`}; `; -const EditorContainer = styled(FauxInput)` - margin-left: ${({ theme }) => theme.space.sm}; - padding: ${({ theme }) => `${theme.space.xxs} ${theme.space.xs}`}; - .ProseMirror { - background-color: #fff; - min-height: 36px; - outline: none; - max-height: 210px; - overflow-y: auto; - - ${editorStyle} - } -`; - /** * CommentBox is a wrapper around Editor component *
@@ -57,34 +37,14 @@ export const CommentBox = ({ placeholderOptions, ...props }: PropsWithChildren) => { - const { children, hasInlineMenu, bubbleOptions, author } = props; - - const { editor, setEditor, triggerSave } = useChatContext(); + const { children, hasInlineMenu, hasButtonsMenu, bubbleOptions, i18n } = + props; + const { editor, setEditor, mentionableUsers, triggerSave } = useChatContext(); - const onKeyDown = (event: ReactKeyboardEvent) => { - if ((event.ctrlKey || event.metaKey) && event.key === "Enter") { - triggerSave(); - editor?.commands.clearContent(); - } - }; + const ext = editorExtensions({ placeholderOptions, mentionableUsers }); const ed = useEditor({ - extensions: [ - Typography, - Link, - StarterKit, - Placeholder.configure({ - placeholder: ({ node }) => { - if (node.type.name === "heading") { - return "What’s the title?"; - } - - return "Can you add some further context?"; - }, - ...placeholderOptions, - }), - CharacterCount, - ], + extensions: ext, content: (children as Content) || "", editorProps: { handleKeyDown: (view, event: KeyboardEvent) => { @@ -98,12 +58,15 @@ export const CommentBox = ({ ...props, }); - if (!ed) { - return null; - } + const onKeyDown = (event: ReactKeyboardEvent) => { + if ((event.ctrlKey || event.metaKey) && event.key === "Enter") { + triggerSave(); + editor?.commands.clearContent(); + } + }; + + if (!ed) return null; - // Add here because we want to keep also the listener from the props. - ed.on("create", ({ editor }) => setEditor(editor as TipTapEditor)); ed.on("update", ({ editor }) => setEditor(editor as TipTapEditor)); return ( @@ -112,16 +75,11 @@ export const CommentBox = ({ )} -
- - {author.avatar} - -
- - +
+ {hasButtonsMenu && } ); }; diff --git a/src/stories/chat/parts/containers.tsx b/src/stories/chat/parts/containers.tsx index 1da65a56..d6c0f7dd 100644 --- a/src/stories/chat/parts/containers.tsx +++ b/src/stories/chat/parts/containers.tsx @@ -1,6 +1,8 @@ import styled from "styled-components"; import { Card } from "../../cards"; import { ChatArgs } from "../_types"; +import { FauxInput } from "@zendeskgarden/react-forms"; +import { editorStyle, readOnlyStyle } from "../../shared/editorStyle"; export const ChatContainer = styled(Card)` padding: ${({ theme }) => `0 ${theme.space.base * 4}px`}; @@ -20,3 +22,24 @@ export const MessagesContainer = styled.div` overflow-y: auto; border-top: ${({ theme }) => `1px solid ${theme.palette.grey[200]}`}; `; + +export const EditorContainer = styled(FauxInput)<{ editable: boolean }>` + ${({ editable, theme }) => + !editable + ? readOnlyStyle + : ` + margin-left: ${theme.space.sm}; + padding: ${`${theme.space.xxs} ${theme.space.xs}`}; + + `} + + .ProseMirror { + background-color: transparent; + min-height: 36px; + outline: none; + max-height: 210px; + overflow-y: auto; + + ${editorStyle} + } +`; \ No newline at end of file diff --git a/src/stories/chat/parts/editorButton.tsx b/src/stories/chat/parts/editorButton.tsx new file mode 100644 index 00000000..1d6f0915 --- /dev/null +++ b/src/stories/chat/parts/editorButton.tsx @@ -0,0 +1,47 @@ +import { Editor } from "@tiptap/react"; +import { ReactComponent as BoldIcon } from "../../../assets/icons/bold-fill.svg"; +import { ReactComponent as ItalicIcon } from "../../../assets/icons/italic-fill.svg"; +import { ReactComponent as MentionIcon } from "../../../assets/icons/at-fill.svg"; +import { IconButton } from "../../buttons/icon-button"; + + +export const EditorButton = ({ + editor, + type, +}: { + editor: Editor; + type: "bold" | "italic" | "mention"; +}) => { + + const isActive = editor.isActive(type); + + const getIcon = () => { + switch (type) { + case "bold": + return ; + case "italic": + return ; + case "mention": + return ; + } + }; + + const handleClick = () => { + switch (type) { + case "bold": + return editor.chain().focus().toggleBold().run(); + case "italic": + return editor.chain().focus().toggleItalic().run(); + case "mention": + const { from } = editor.state.selection; + const char = from > 1 ? " @" : "@"; + return editor.commands.insertContent(char); + } + }; + + return ( + + {getIcon()} + + ); +}; diff --git a/src/stories/chat/parts/extensions.tsx b/src/stories/chat/parts/extensions.tsx new file mode 100644 index 00000000..16a131ca --- /dev/null +++ b/src/stories/chat/parts/extensions.tsx @@ -0,0 +1,131 @@ +import { ReactRenderer } from "@tiptap/react"; +import { PlaceholderOptions } from "@tiptap/extension-placeholder"; +import Typography from "@tiptap/extension-typography"; +import Link from "@tiptap/extension-link"; +import StarterKit from "@tiptap/starter-kit"; +import Placeholder from "@tiptap/extension-placeholder"; +import CharacterCount from "@tiptap/extension-character-count"; +import { CustomMention as Mention } from "./mention"; +import { MentionList, MentionListRef } from "./mentionList"; +import tippy, { type Instance as TippyInstance } from "tippy.js"; +import { SuggestedUser } from "../_types"; + +/** + * Workaround for the current typing incompatibility between Tippy.js and Tiptap + * Suggestion utility. + * + * @see https://github.com/ueberdosis/tiptap/issues/2795#issuecomment-1160623792 + * + * Adopted from + * https://github.com/Doist/typist/blob/a1726a6be089e3e1452def641dfcfc622ac3e942/stories/typist-editor/constants/suggestions.ts#L169-L186 + */ +const DOM_RECT_FALLBACK: DOMRect = { + bottom: 0, + height: 0, + left: 0, + right: 0, + top: 0, + width: 0, + x: 0, + y: 0, + toJSON() { + return {}; + }, +}; + +export const editorExtensions = ({ + placeholderOptions, + mentionableUsers, +}: { + mentionableUsers: (props: { query: string }) => Promise; + placeholderOptions?: Partial; +}) => { + return [ + Typography, + Link, + StarterKit, + Placeholder.configure({ + placeholder: ({ node }) => { + if (node.type.name === "heading") { + return "What’s the title?"; + } + + return "Can you add some further context?"; + }, + ...placeholderOptions, + }), + CharacterCount, + Mention.configure({ + suggestion: { + items: mentionableUsers, + render: () => { + let component: ReactRenderer | undefined; + let popup: TippyInstance | undefined; + + return { + onStart: (props) => { + component = new ReactRenderer(MentionList, { + props, + editor: props.editor, + }); + + if (!props.clientRect) { + return; + } + + popup = tippy("body", { + getReferenceClientRect: () => + props.clientRect?.() ?? DOM_RECT_FALLBACK, + appendTo: () => document.body, + content: component.element, + showOnCreate: true, + interactive: true, + trigger: "manual", + placement: "auto", + })[0]; + }, + + onUpdate(props) { + component?.updateProps(props); + + if (!props.clientRect) { + return; + } + + popup?.setProps({ + getReferenceClientRect: () => + props.clientRect?.() ?? DOM_RECT_FALLBACK, + }); + }, + + onKeyDown(props) { + if (props.event.key === "Escape") { + popup?.hide(); + + return true; + } + + if (!component?.ref) { + return false; + } + return component.ref.onKeyDown(props); + }, + + onExit() { + popup?.destroy(); + component?.destroy(); + + // Remove references to the old popup and component upon destruction/exit. + // (This should prevent redundant calls to `popup.destroy()`, which Tippy + // warns in the console is a sign of a memory leak, as the `suggestion` + // plugin seems to call `onExit` both when a suggestion menu is closed after + // a user chooses an option, *and* when the editor itself is destroyed.) + popup = undefined; + component = undefined; + }, + }; + }, + }, + }), + ]; +}; diff --git a/src/stories/chat/parts/footer.tsx b/src/stories/chat/parts/footer.tsx index dea3f771..1bda346e 100644 --- a/src/stories/chat/parts/footer.tsx +++ b/src/stories/chat/parts/footer.tsx @@ -1,21 +1,36 @@ import styled from "styled-components"; import { PropsWithChildren } from "react"; +import { SendShortcut } from "./sendShortcut"; -const Footer = styled.div` +const Footer = styled.div<{ showShortcut?: boolean }>` display: flex; flex-direction: row; - padding: ${({ theme }) => `${theme.space.sm} 0`}; - justify-content: flex-end; + padding: ${({ theme }) => `${theme.space.sm} 0px`}; + align-items: center; + margin: ${({ theme }) => `0 -${theme.space.base * 4}px`}; + justify-content: ${({ showShortcut }) => + showShortcut ? "space-between" : "flex-end"}; gap: ${({ theme }) => theme.space.xs}; + background-color: ${({ theme }) => theme.palette.grey[100]}; `; export const ChatFooter = ({ saveText, children, -}: PropsWithChildren<{ saveText?: string }>) => { + showShortcut, +}: PropsWithChildren<{ saveText?: string; showShortcut?: boolean }>) => { return ( <> -
{children}
+
+ {showShortcut ? ( + <> + + {children} + + ) : ( + children + )} +
); }; diff --git a/src/stories/chat/parts/mention.ts b/src/stories/chat/parts/mention.ts new file mode 100644 index 00000000..76de4617 --- /dev/null +++ b/src/stories/chat/parts/mention.ts @@ -0,0 +1,73 @@ +import { mergeAttributes } from "@tiptap/core"; +import Mention from "@tiptap/extension-mention"; +import { SuggestedUser } from "../_types"; + +export const CustomMention = Mention.extend({ + addAttributes() { + return { + id: { + default: null, + parseHTML: (element) => element.getAttribute("data-mention-id"), + renderHTML: (attributes) => { + if (!attributes.id) { + return {}; + } + + return { + "data-mention-id": attributes.id, + }; + }, + }, + name: { + default: null, + parseHTML: (element) => element.getAttribute("data-mention-name"), + renderHTML: (attributes) => { + if (!attributes.name) { + return {}; + } + + return { + "data-mention-name": attributes.name, + }; + }, + }, + }; + }, + parseHTML() { + return [ + { + tag: `mention[data-type="${this.name}"]`, + }, + ]; + }, + + renderHTML({ node, HTMLAttributes }) { + // In the future we can fetch other user data here, like avatar, user role, etc. + const user = node.attrs as SuggestedUser; + let outputText = "unkown"; + + if (user) { + outputText = `${this.options.suggestion.char}${user.name}`; + } + + return [ + "mention", + mergeAttributes( + { "data-type": this.name }, + this.options.HTMLAttributes, + HTMLAttributes + ), + outputText, + ]; + }, + + renderText({ node }) { + const user = node.attrs as SuggestedUser; + + if (user) { + return `${this.options.suggestion.char}${user.name}`; + } + + return "unkown"; + }, +}); diff --git a/src/stories/chat/parts/mentionList.tsx b/src/stories/chat/parts/mentionList.tsx new file mode 100644 index 00000000..3a7bc052 --- /dev/null +++ b/src/stories/chat/parts/mentionList.tsx @@ -0,0 +1,122 @@ +import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; +import type { SuggestionOptions, SuggestionProps } from "@tiptap/suggestion"; +import { Card } from "../../cards"; +import { Button } from "../../buttons/button"; +import { styled } from "styled-components"; +import { SuggestedUser } from "../_types"; +import { MD } from "../../typography/typescale"; +import { theme } from "../../theme"; + +export type MentionListRef = { + onKeyDown: NonNullable< + ReturnType< + NonNullable["render"]> + >["onKeyDown"] + >; +}; + +const StyledCard = styled(Card)` + padding: ${({ theme }) => theme.space.xxs}; + + ::-webkit-scrollbar { + width: ${({ theme }) => theme.space.xs}; + } +`; + +const List = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.space.xs}; + max-height: 200px; + overflow-y: auto; +`; + +const Item = styled(Button)` + display: flex; + align-items: flex-start; + justify-content: center; + flex-direction: column; +`; + + +type MentionListProps = SuggestionProps; + +export const MentionList = forwardRef( + (props, ref) => { + const [selectedIndex, setSelectedIndex] = useState(0); + + const selectItem = (index: number) => { + const item = props.items[index]; + + if (item) { + props.command(item); + } + }; + + const upHandler = () => { + setSelectedIndex( + (selectedIndex + props.items.length - 1) % props.items.length + ); + }; + + const downHandler = () => { + setSelectedIndex((selectedIndex + 1) % props.items.length); + }; + + const enterHandler = () => { + selectItem(selectedIndex); + }; + + useEffect(() => setSelectedIndex(0), [props.items]); + + useImperativeHandle(ref, () => ({ + onKeyDown: ({ event }) => { + if (event.key === "ArrowUp") { + upHandler(); + return true; + } + + if (event.key === "ArrowDown") { + downHandler(); + return true; + } + + if (event.key === "Enter") { + enterHandler(); + return true; + } + + return false; + }, + })); + + return ( +
+ + + {props.items.length ? ( + props.items.map((item, index) => ( +
+ selectItem(index)} + > + + {item.name} + + +
+ )) + ) : ( +
No result
+ )} +
+
+
+ ); + } +); diff --git a/src/stories/chat/parts/sendShortcut.tsx b/src/stories/chat/parts/sendShortcut.tsx new file mode 100644 index 00000000..49b06084 --- /dev/null +++ b/src/stories/chat/parts/sendShortcut.tsx @@ -0,0 +1,24 @@ +import { Tag } from "@zendeskgarden/react-tags"; +import { SM } from "@zendeskgarden/react-typography"; +import styled from "styled-components"; +import { isMac } from "../../theme/utils"; + +const ShortcutContainer = styled.div` + display: flex; + flex-direction: row; + padding: ${({ theme }) => `${theme.space.sm} ${theme.space.base * 4}px`}; + background-color: ${({ theme }) => theme.palette.grey[100]}; +`; + +const Text = styled(SM)` + line-height: 1.7; +`; + +export const SendShortcut = ({ saveText }: { saveText?: string }) => { + return ( + + {isMac() ? "Cmd" : "Ctrl"}+enter  + {saveText || "to save"} + + ); +}; diff --git a/src/stories/editor/_types.tsx b/src/stories/editor/_types.tsx index 7ba911db..3db3a240 100644 --- a/src/stories/editor/_types.tsx +++ b/src/stories/editor/_types.tsx @@ -1,7 +1,7 @@ import { PlaceholderOptions } from "@tiptap/extension-placeholder"; import { BubbleMenuProps, Editor, EditorOptions } from "@tiptap/react"; -type validationStatus= "success" | "warning" | "error"; +type validationStatus = "success" | "warning" | "error"; export interface EditorArgs extends Partial { placeholderOptions?: Partial; diff --git a/src/stories/editor/editorFooter.tsx b/src/stories/editor/editorFooter.tsx index e9697419..827461a1 100644 --- a/src/stories/editor/editorFooter.tsx +++ b/src/stories/editor/editorFooter.tsx @@ -3,6 +3,7 @@ import { Tag } from "../tags"; import { isMac } from "../theme/utils"; import { SM } from "../typography/typescale"; + const Footer = styled.div` display: flex; flex-direction: row; diff --git a/src/stories/editor/floatingMenu.tsx b/src/stories/editor/floatingMenu.tsx index a51a9d45..3fa17725 100644 --- a/src/stories/editor/floatingMenu.tsx +++ b/src/stories/editor/floatingMenu.tsx @@ -40,7 +40,7 @@ export const FloatingMenu = (props: FloatingMenuArgs) => { if (!editor) { return null; } - + return ( diff --git a/src/stories/shared/editorStyle.tsx b/src/stories/shared/editorStyle.tsx index 0e77165a..c7277874 100644 --- a/src/stories/shared/editorStyle.tsx +++ b/src/stories/shared/editorStyle.tsx @@ -236,7 +236,7 @@ export const editorStyle = css` } /* Placeholder (at the top) */ - /*p.is-editor-empty:first-child::before { + /*p.is-editor-empty:first-child::before { content: attr(data-placeholder); float: left; color: #ced4da; @@ -254,4 +254,24 @@ export const editorStyle = css` } word-break: break-word; + + mention { + color: ${({ theme }) => theme.palette.azure[600]}; + border-radius: ${({ theme }) => theme.borderRadii.xl}; + font-weight: ${({ theme }) => theme.fontWeights.semibold}; + padding: ${({ theme }) => `${theme.space.xxs} 0`}; + } `; + +export const readOnlyStyle = css` + border: none; + outline: none; + + .ProseMirror { + background: transparent; + border: none; + outline: none; + padding: 0; + min-height: 0; + } +`; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 2e50f7d6..43d2d3ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3286,141 +3286,146 @@ dependencies: "@babel/runtime" "^7.12.5" -"@tiptap/core@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.3.tgz#dfd55124b3e7b0482e5ccb8be46eb9c3189167e2" - integrity sha512-jLyVIWAdjjlNzrsRhSE2lVL/7N8228/1R1QtaVU85UlMIwHFAcdzhD8FeiKkqxpTnGpaDVaTy7VNEtEgaYdCyA== - -"@tiptap/extension-blockquote@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.3.tgz#3ee7aff66a2526501154ca69f3e91e153c58313c" - integrity sha512-rkUcFv2iL6f86DBBHoa4XdKNG2StvkJ7tfY9GoMpT46k3nxOaMTqak9/qZOo79TWxMLYtXzoxtKIkmWsbbcj4A== - -"@tiptap/extension-bold@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.3.tgz#2a28816195562a39c33f50e626796d14a800784f" - integrity sha512-OGT62fMRovSSayjehumygFWTg2Qn0IDbqyMpigg/RUAsnoOI2yBZFVrdM2gk1StyoSay7gTn2MLw97IUfr7FXg== - -"@tiptap/extension-bubble-menu@^2.0.0-beta.61", "@tiptap/extension-bubble-menu@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.3.tgz#44b3c4e35fd478c42467d8fb7dbc9532614e5b18" - integrity sha512-lPt1ELrYCuoQrQEUukqjp9xt38EwgPUwaKHI3wwt2Rbv+C6q1gmRsK1yeO/KqCNmFxNqF2p9ZF9srOnug/RZDQ== +"@tiptap/core@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.16.tgz#828dd34aa9f49574f1eb7b311c0d69b3a9aacf27" + integrity sha512-nKnV603UyzbcrqhCXTWxDN22Ujb4VNfmKkACms1JOMGo7BVARmMCp2nBsLW8fmgCxmf8AS0LXY63tU7ILWYc5g== + +"@tiptap/extension-blockquote@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.1.16.tgz#79e77a4b3d479f02c1ff906a3fd262045925bf6f" + integrity sha512-1OMk8cBrL0VnbnzD3XHx7U4oMDCiXRR7Spfl9JqwC9pS4RosOUBySNxpEBwhSegB0pK6sd7m44qLqj00If+cHA== + +"@tiptap/extension-bold@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.1.16.tgz#6c8cab89a4385fe3f4847fd95ead5355e3d890ab" + integrity sha512-gz2VrBkRRsGBiOHx1qB++VUfpuRdhJp6jlgNqqHFbIkjKr2NB+u7oiH5SbjlL4eG0wlam1QA4jAkXhZgdvkA4g== + +"@tiptap/extension-bubble-menu@2.1.16", "@tiptap/extension-bubble-menu@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.16.tgz#4997de2811ee96648d1b5ba2148bc223840f7db5" + integrity sha512-MwKCmu2kU7+Xln/BvlrolU2hCXgoCoTr4NXJ+3v8A9w7tIq8leADoWacfEee2t3VNnGdXw/Xjza+DAr77JWjGg== dependencies: tippy.js "^6.3.7" -"@tiptap/extension-bullet-list@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.3.tgz#43c4c0c161d5c065f3f87e4bf54d13bd6c55b4c3" - integrity sha512-RtaLiRvZbMTOje+FW5bn+mYogiIgNxOm065wmyLPypnTbLSeHeYkoqVSqzZeqUn+7GLnwgn1shirUe6csVE/BA== - -"@tiptap/extension-character-count@^2.0.0-beta.31": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-character-count/-/extension-character-count-2.0.3.tgz#40926fef6d3cd82586b1f6c4c644d020e5f42139" - integrity sha512-Ge4aUmgYOmQR/HLPkbQSFKEywyRu6IalHAQmH3laY6LB9qrmT90AoaiFnaVCDpphYFQ7RygnBXJMgjtJ3WpZmw== - -"@tiptap/extension-code-block@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.3.tgz#4ce08b4f3c5af166d3cc00e91ba5b989f01fee63" - integrity sha512-F4xMy18EwgpyY9f5Te7UuF7UwxRLptOtCq1p2c2DfxBvHDWhAjQqVqcW/sq/I/WuED7FwCnPLyyAasPiVPkLPw== - -"@tiptap/extension-code@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.3.tgz#74d88073faedd1fc52d6ed3de4eed8fde80ff4bf" - integrity sha512-LsVCKVxgBtkstAr1FjxN8T3OjlC76a2X8ouoZpELMp+aXbjqyanCKzt+sjjUhE4H0yLFd4v+5v6UFoCv4EILiw== - -"@tiptap/extension-document@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.3.tgz#b58af5b4f71c0acea953a7ebe8b1d24341bfaf68" - integrity sha512-PsYeNQQBYIU9ayz1R11Kv/kKNPFNIV8tApJ9pxelXjzcAhkjncNUazPN/dyho60mzo+WpsmS3ceTj/gK3bCtWA== - -"@tiptap/extension-dropcursor@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.3.tgz#205d02c70b200810572d0b7e264bbdb718343ad0" - integrity sha512-McthMrfusn6PjcaynJLheZJcXto8TaIW5iVitYh8qQrDXr31MALC/5GvWuiswmQ8bAXiWPwlLDYE/OJfwtggaw== - -"@tiptap/extension-floating-menu@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.3.tgz#8d9943246aa3247442c1993f235617094fe705b5" - integrity sha512-zN1vRGRvyK3pO2aHRmQSOTpl4UJraXYwKYM009n6WviYKUNm0LPGo+VD4OAtdzUhPXyccnlsTv2p6LIqFty6Bg== +"@tiptap/extension-bullet-list@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.16.tgz#fabd6c024721e2424f256d00992f9ca414522a5d" + integrity sha512-Cheaep5JShO9TtRslrOObSVKtRQFKozou2ZWDas5sIeef/A/GWPfVTzusfBGE/ItHwZNaDXwJOoVnSUPT8ulfw== + +"@tiptap/extension-character-count@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-character-count/-/extension-character-count-2.1.16.tgz#ccff5a34a88fe74ce23eda2d76864f5c8dca331d" + integrity sha512-8ccyiZ3YOz1GXbCPOQ6dOlBBRDDyqyJILSriWN8QXpcReD692lm1zOI6qPhIvKYIluV/8eBx59IYpx/u8IoPKQ== + +"@tiptap/extension-code-block@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.1.16.tgz#aa197def308d9baaaf52f79f55ef103e44d561ce" + integrity sha512-IspVmwg17Vx59W8lEIbVRIeMscJtRCdsif45CkzVv1uSHMl7tmrJh3n8vv/vrB+rnLasQrOEbEKNEqUL3eHlKQ== + +"@tiptap/extension-code@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.1.16.tgz#2e98008001ac55e9a25a4fc1fd9eed639b6dc433" + integrity sha512-2+fVfh3qQORgMRSZ6hn+yW5/rLzlulCzMhdL07G0lWY8/eWEv3p9DCfgw9AOHrrHFim8/MVWyRkrkBM/yHX9FA== + +"@tiptap/extension-document@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.1.16.tgz#7e2ddf28ac32318aee439b6ee01c8c898be9be14" + integrity sha512-VSOrzGnpI9dJDffFn3ZjmPKYkH/YtYeDl6nqLu7TafRqyLMSEqxxxq/+Qs/7j8jbzq6osslY0sySckSulroIOg== + +"@tiptap/extension-dropcursor@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.16.tgz#6b7d3d3cb0375bba7499c6f9223562008ec983c5" + integrity sha512-voWEIAmxV3f9Q0gc3K89HRq8KFeOVtHJBRHYihZwxMnvq2aMIwdpCx0GbiCd4slQaBLd1ASJHz1uAigVhR2+uA== + +"@tiptap/extension-floating-menu@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.16.tgz#1ee53b1244ff23653114fdc248d6856a2eaa7974" + integrity sha512-VBT4HBhkKr9S1VExyTb/qfQyZ5F0VJLasUoH8E4kdq3deCeifmTTIOukuXK5QbicFHVQmY2epeU6+w5c/bAcHQ== dependencies: tippy.js "^6.3.7" -"@tiptap/extension-gapcursor@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.3.tgz#e098b78c4a169e1630dc6531d68b7f365de59c2f" - integrity sha512-6I9EzzsYOyyqDvDvxIK6Rv3EXB+fHKFj8ntHO8IXmeNJ6pkhOinuXVsW6Yo7TcDYoTj4D5I2MNFAW2rIkgassw== - -"@tiptap/extension-hard-break@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.3.tgz#aa7805d825e5244bdccc508da18c781e231b2859" - integrity sha512-RCln6ARn16jvKTjhkcAD5KzYXYS0xRMc0/LrHeV8TKdCd4Yd0YYHe0PU4F9gAgAfPQn7Dgt4uTVJLN11ICl8sQ== - -"@tiptap/extension-heading@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.3.tgz#5e9e779f33f366afcf729d9f68ef49721f825e11" - integrity sha512-f0IEv5ms6aCzL80WeZ1qLCXTkRVwbpRr1qAETjg3gG4eoJN18+lZNOJYpyZy3P92C5KwF2T3Av00eFyVLIbb8Q== - -"@tiptap/extension-history@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.3.tgz#8936c15aa46f2ddeada1c3d9abe2888d58d08c30" - integrity sha512-00KHIcJ8kivn2ARI6NQYphv2LfllVCXViHGm0EhzDW6NQxCrriJKE3tKDcTFCu7LlC5doMpq9Z6KXdljc4oVeQ== - -"@tiptap/extension-horizontal-rule@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.3.tgz#5c67db2c0bf3bc14a8aab80df584bee5aa23fbeb" - integrity sha512-SZRUSh07b/M0kJHNKnfBwBMWrZBEm/E2LrK1NbluwT3DBhE+gvwiEdBxgB32zKHNxaDEXUJwUIPNC3JSbKvPUA== - -"@tiptap/extension-italic@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.3.tgz#2d9d5d8ccf3c38266f745029c2ec0646c075c1fc" - integrity sha512-cfS5sW0gu7qf4ihwnLtW/QMTBrBEXaT0sJl3RwkhjIBg/65ywJKE5Nz9ewnQHmDeT18hvMJJ1VIb4j4ze9jj9A== - -"@tiptap/extension-link@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.4.tgz#2899f9060ca722f11bd10ceb572ceb5178f111d6" - integrity sha512-CliImI1hmC+J6wHxqgz9P4wMjoNSSgm3fnNHsx5z0Bn6JRA4Evh2E3KZAdMaE8xCTx89rKxMYNbamZf4VLSoqQ== +"@tiptap/extension-gapcursor@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.16.tgz#6618458314bb69e1d43c1b9fc4858c8bcd2a5ac8" + integrity sha512-Bgjo0da0W1QOhtnT3NR7GHPmVBZykNRekNGsTA3+nxCjkqh1G32Jt58TBKP3vdLBaww3lhrii0SYVErlFgIJnA== + +"@tiptap/extension-hard-break@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.1.16.tgz#532618d9607fc328c25ea486b64656b8f6841b04" + integrity sha512-H3Bk8Gu5pV7xH8TrzH0WAoXrJVEKsDA6Evyl7H7aCAMAvotQL0ehuuX88bjPMCSAvBXZE39wYnJCJshGbVx0BA== + +"@tiptap/extension-heading@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.1.16.tgz#759f7d6bc48be6388f81252a81ef074bd3935b8a" + integrity sha512-vFQuAAnIxDwKjTV+ScSwIaeG4Uhm1cZddnbLTru1EJfIz9VvpHDZKEyL4ZJvWuKMAhCzlw54TQhBCVHqalXyaA== + +"@tiptap/extension-history@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.1.16.tgz#22d130d5b8bd4133d43be3a32c6bb26b863e5710" + integrity sha512-9YHPf8Xqqp5CQy1hJonkBzROj0ZHR1ZaIk9IaLlAPTpdkrUDXV9SC7qp3lozQsMg4vmU3K6H5VQo4ADpnR00OQ== + +"@tiptap/extension-horizontal-rule@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.16.tgz#484a5c29d4e708cefc50b05dccc4489e140a3fa1" + integrity sha512-Q+Zp0lJF7212YIuZnbMmn4KC1MZoZjQIuvSd+DOgCwKSeUcTXBbljDjOiN8yrY134r+A4fFM7KHTXWYqZGZQug== + +"@tiptap/extension-italic@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.1.16.tgz#742daedcc08cafe6a3252b9d29822e7d8ef70887" + integrity sha512-6mFGPBGxd2aICJ5Q3zYxuXO8slKoOP/PsSjEQn1bjs3h8Q3mPxHX290ePVp728o5F0myM9sxKSz2V6/VeuS/Yw== + +"@tiptap/extension-link@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.1.16.tgz#cd4c57135c1c849e0dfdd2caa7eb060d9a7f90a8" + integrity sha512-QIXYwxHi2kKU2sqDXngTpggO4ZmLm4vMxDlbWT9so1iUPAqQJW2ZRbdygFYy1txirWcoaJKocPwSJemyAeUzmw== dependencies: linkifyjs "^4.1.0" -"@tiptap/extension-list-item@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.3.tgz#2bca673b1ed83fdc00cb208f4d5c57d4d44ddb22" - integrity sha512-p7cUsk0LpM1PfdAuFE8wYBNJ3gvA0UhNGR08Lo++rt9UaCeFLSN1SXRxg97c0oa5+Ski7SrCjIJ5Ynhz0viTjQ== - -"@tiptap/extension-ordered-list@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.3.tgz#d1e5d6fc240545dbba7f7e6666bebd658fc3b4ad" - integrity sha512-ZB3MpZh/GEy1zKgw7XDQF4FIwycZWNof1k9WbDZOI063Ch4qHZowhVttH2mTCELuyvTMM/o9a8CS7qMqQB48bw== - -"@tiptap/extension-paragraph@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.3.tgz#88d332158c70622d36849256f90e43ca4d226dfe" - integrity sha512-a+tKtmj4bU3GVCH1NE8VHWnhVexxX5boTVxsHIr4yGG3UoKo1c5AO7YMaeX2W5xB5iIA+BQqOPCDPEAx34dd2A== - -"@tiptap/extension-placeholder@^2.0.0-beta.53": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.0.3.tgz#69575353f09fc7524c9cdbfbf16c04f73c29d154" - integrity sha512-Z42jo0termRAf0S0L8oxrts94IWX5waU4isS2CUw8xCUigYyCFslkhQXkWATO1qRbjNFLKN2C9qvCgGf4UeBrw== - -"@tiptap/extension-strike@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.3.tgz#4ec0001db5f51f86d06da22364114f20f073d4b3" - integrity sha512-RO4/EYe2iPD6ifDHORT8fF6O9tfdtnzxLGwZIKZXnEgtweH+MgoqevEzXYdS+54Wraq4TUQGNcsYhe49pv7Rlw== - -"@tiptap/extension-text@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.3.tgz#12b6400a31ac6d35cbaf1822600f4c425457902f" - integrity sha512-LvzChcTCcPSMNLUjZe/A9SHXWGDHtvk73fR7CBqAeNU0MxhBPEBI03GFQ6RzW3xX0CmDmjpZoDxFMB+hDEtW1A== - -"@tiptap/extension-typography@^2.0.0-beta.22": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/extension-typography/-/extension-typography-2.0.3.tgz#fc3e9a5e15cfd9fa041315437a46681c3b0b5d51" - integrity sha512-5U91O2dffYOvwenWG+zT1N/pnt+RppSlocxs1KaNWFLlI2fgzDTyUyjzygIHGmskStqay2MuvmPnfVABoC+1Gw== - -"@tiptap/pm@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.0.3.tgz#e8bb47df765fc1b7acd52f2800c52d7ff945c5ec" - integrity sha512-I9dsInD89Agdm1QjFRO9dmJtU1ldVSILNPW0pEhv9wYqYVvl4HUj/JMtYNqu2jWrCHNXQcaX/WkdSdvGJtmg5g== +"@tiptap/extension-list-item@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.16.tgz#f098f58b027c498ce1bac8d2b8e9b85d04383818" + integrity sha512-RLUodzFispIe1adarCEzf+OfaGZna/WR/k/HqPnbflSiJ6/I2P5MqI+ELjGGvc53eanf3+KpsHlB2Pganp8sMA== + +"@tiptap/extension-mention@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-mention/-/extension-mention-2.1.16.tgz#87f45f3ac1bce43bd2306954ef8e423190474997" + integrity sha512-Egw8ZDyIEazhhI4VUErOVjnNhQMRz2MY0o2WXHKTNiSLkWcHJJDkyUgRu5b5BphjQcXNRAz0rifPaFENY0iLqg== + +"@tiptap/extension-ordered-list@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.16.tgz#8c66157bfd5158a41f8678490e08c59cd305aa6a" + integrity sha512-6QLUm90wz2lfzWGV4fX5NOOFA8zKlcDBNrTeOd0V7H4fcafLmANqU/5o4LLNJmK8y8f1YAvmHr9xgciqggGJJA== + +"@tiptap/extension-paragraph@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.1.16.tgz#736be2551343bedb71277eadc3544af7b28149bc" + integrity sha512-JwCKSFjBLd9xAmxLe7hf1h4AucDvkGTfDb/wA1jId64g+uf0/tm6RDjnk/QD+D2YzoLGFLjQm0GAdPXTmyTPdA== + +"@tiptap/extension-placeholder@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-placeholder/-/extension-placeholder-2.1.16.tgz#9465490402ea85677bbc42743e02dcecb12059a7" + integrity sha512-C6xgWKn6LT7yhvz0RCHjzFxpstFCHUw2eXisrHlOz36SP/1EmGIBiKqJUP7ySSSQMgl4hzHDhj6W1KyGdsyYaA== + +"@tiptap/extension-strike@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.1.16.tgz#cde257906c9815fd90aa74d6f8ea7325881c1c8d" + integrity sha512-Z1hmBK1QWMMGDV2MJ3KBDiMuzcScjyx88cP5ln5G7626Zxeqywf84KF+2WyHBzJWfwMWpAouzwHKe9ld39Vu1w== + +"@tiptap/extension-text@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.1.16.tgz#188d46545450f1b1969d70853f24ab14437827ec" + integrity sha512-XzSJmAj32uYpaL/9FX3tRSU52DwZ9w+3yEffIcSN9MSwioqLsSolXOz7TuJfW6lSTar1ml9UPlRqX4dpayUTDQ== + +"@tiptap/extension-typography@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/extension-typography/-/extension-typography-2.1.16.tgz#55cd1f62c9cb6b7004d0c21c8d2a76f7113fa809" + integrity sha512-1lQzgcnviYV/wm78YB4CIuVI4is0bLGxxEjrDjOexEYINbBJAEESYeUZjwVyU7VKJRWO/0rpwtFJ7D/BVibMkQ== + +"@tiptap/pm@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-2.1.16.tgz#1f43024e2660f2a9fe3057b71e57f0550f7d3387" + integrity sha512-yibLkjtgbBSnWCXbDyKM5kgIGLfMvfbRfFzb8T0uz4PI/L54o0a4fiWSW5Fg10B5+o+NAXW2wMxoId8/Tw91lQ== dependencies: prosemirror-changeset "^2.2.0" prosemirror-collab "^1.3.0" @@ -3441,38 +3446,43 @@ prosemirror-transform "^1.7.0" prosemirror-view "^1.28.2" -"@tiptap/react@^2.0.0-beta.114": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.0.3.tgz#4b7155ed4bfe3fa9cb691adbbcf3713173ca7a6c" - integrity sha512-fiAh8Lk+/NBPAR/PE4Kc/aLiBUbUYI/CpAopz8DI9eInNyV8h8LAGa9uFILJQF/TNu0tclJ4rV0sWc7Se0FZMw== - dependencies: - "@tiptap/extension-bubble-menu" "^2.0.3" - "@tiptap/extension-floating-menu" "^2.0.3" - -"@tiptap/starter-kit@^2.0.0-beta.191": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.0.3.tgz#e9a7e26f981ee5295762d796f02e1794c247d417" - integrity sha512-t4WG4w93zTpL2VxhVyJJvl3kdLF001ZrhpOuEiZqEMBMUMbM56Uiigv1CnUQpTFrjDAh3IM8hkqzAh20TYw2iQ== - dependencies: - "@tiptap/core" "^2.0.3" - "@tiptap/extension-blockquote" "^2.0.3" - "@tiptap/extension-bold" "^2.0.3" - "@tiptap/extension-bullet-list" "^2.0.3" - "@tiptap/extension-code" "^2.0.3" - "@tiptap/extension-code-block" "^2.0.3" - "@tiptap/extension-document" "^2.0.3" - "@tiptap/extension-dropcursor" "^2.0.3" - "@tiptap/extension-gapcursor" "^2.0.3" - "@tiptap/extension-hard-break" "^2.0.3" - "@tiptap/extension-heading" "^2.0.3" - "@tiptap/extension-history" "^2.0.3" - "@tiptap/extension-horizontal-rule" "^2.0.3" - "@tiptap/extension-italic" "^2.0.3" - "@tiptap/extension-list-item" "^2.0.3" - "@tiptap/extension-ordered-list" "^2.0.3" - "@tiptap/extension-paragraph" "^2.0.3" - "@tiptap/extension-strike" "^2.0.3" - "@tiptap/extension-text" "^2.0.3" +"@tiptap/react@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.1.16.tgz#20addbd7a0abac8942d2aea83dcb922c44d7ca9a" + integrity sha512-ywws50aV/8TNBBlFNXVR/bWpi5n0f+CDiwixsPGzne9T/zMfz30FnXej2xo7Qm1LLBdC0d65Mz7otdz+rn65cA== + dependencies: + "@tiptap/extension-bubble-menu" "^2.1.16" + "@tiptap/extension-floating-menu" "^2.1.16" + +"@tiptap/starter-kit@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.1.16.tgz#1da99d2b6de8a408c3be5aea86b7747454dff4fa" + integrity sha512-DudGvkNEB1IwfMAqBKCcT49BY275hKF6SwjTWN89cLvVBd2TBe4R6wWMNKDhwfR8fmXz/aXpGJWWO2AFimY3jg== + dependencies: + "@tiptap/core" "^2.1.16" + "@tiptap/extension-blockquote" "^2.1.16" + "@tiptap/extension-bold" "^2.1.16" + "@tiptap/extension-bullet-list" "^2.1.16" + "@tiptap/extension-code" "^2.1.16" + "@tiptap/extension-code-block" "^2.1.16" + "@tiptap/extension-document" "^2.1.16" + "@tiptap/extension-dropcursor" "^2.1.16" + "@tiptap/extension-gapcursor" "^2.1.16" + "@tiptap/extension-hard-break" "^2.1.16" + "@tiptap/extension-heading" "^2.1.16" + "@tiptap/extension-history" "^2.1.16" + "@tiptap/extension-horizontal-rule" "^2.1.16" + "@tiptap/extension-italic" "^2.1.16" + "@tiptap/extension-list-item" "^2.1.16" + "@tiptap/extension-ordered-list" "^2.1.16" + "@tiptap/extension-paragraph" "^2.1.16" + "@tiptap/extension-strike" "^2.1.16" + "@tiptap/extension-text" "^2.1.16" + +"@tiptap/suggestion@2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.1.16.tgz#bc0c340b8989a01fcf800b379092a65c426fdbe9" + integrity sha512-3kYgpT1oTSgjLesAU3rV3lkcqVRV9K520/tA1IhXAC+UsofUEkflXftoMnaJjwgEfKM3n87uJlyPFEUBiC7xYg== "@tootallnate/once@1": version "1.1.2" @@ -5473,9 +5483,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464: - version "1.0.30001572" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001572.tgz" - integrity sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw== + version "1.0.30001579" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz" + integrity sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA== case-anything@^2.1.10: version "2.1.11"