-
-
Notifications
You must be signed in to change notification settings - Fork 139
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #850 from The-Commit-Company/fix-mobile-app-renderer
fix: use tiptap renderer on mobile app
- Loading branch information
Showing
10 changed files
with
363 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Blockquote.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Blockquote } from '@radix-ui/themes'; | ||
import TiptapBlockquote from '@tiptap/extension-blockquote' | ||
import { NodeViewRendererProps, NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react"; | ||
|
||
export const CustomBlockquote = TiptapBlockquote.extend({ | ||
addNodeView() { | ||
return ReactNodeViewRenderer(BlockquoteRenderer) | ||
} | ||
}) | ||
|
||
const BlockquoteRenderer = ({ node }: NodeViewRendererProps) => { | ||
return ( | ||
<NodeViewWrapper> | ||
<Blockquote m='2' size='3'> | ||
{node.textContent} | ||
</Blockquote> | ||
</NodeViewWrapper> | ||
); | ||
}; |
14 changes: 14 additions & 0 deletions
14
mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Bold.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import TiptapBold from '@tiptap/extension-bold' | ||
import { mergeAttributes } from "@tiptap/react"; | ||
|
||
export const CustomBold = TiptapBold.extend({ | ||
renderHTML({ HTMLAttributes }) { | ||
return [ | ||
"strong", | ||
mergeAttributes(HTMLAttributes, { | ||
class: 'rt-Strong' | ||
}), // mergeAttributes is a exported function from @tiptap/core | ||
0, | ||
]; | ||
}, | ||
}) |
127 changes: 127 additions & 0 deletions
127
mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Link.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// import { Skeleton } from '@/components/common/Skeleton'; | ||
// import { Box, Flex, Text } from '@radix-ui/themes'; | ||
import TiptapLink from '@tiptap/extension-link' | ||
import { mergeAttributes, useCurrentEditor } from "@tiptap/react"; | ||
// import { useFrappeGetCall } from 'frappe-react-sdk'; | ||
// import { memo, useMemo } from 'react'; | ||
|
||
export const CustomLink = TiptapLink.extend({ | ||
renderHTML({ HTMLAttributes }) { | ||
return [ | ||
"a", | ||
mergeAttributes(HTMLAttributes, { | ||
class: 'rt-Text rt-reset rt-Link rt-underline-auto break-all' | ||
}), // mergeAttributes is a exported function from @tiptap/core | ||
0, | ||
]; | ||
}, | ||
}).configure({ | ||
protocols: ['mailto', 'https', 'http'], | ||
openOnClick: false, | ||
}) | ||
|
||
export type LinkPreviewDetails = { | ||
title: string, | ||
description: string, | ||
image: string, | ||
force_title: string, | ||
absolute_image: string, | ||
site_name: string | ||
} | ||
|
||
// export const LinkPreview = memo(({ isScrolling }: { isScrolling?: boolean }) => { | ||
|
||
// const { editor } = useCurrentEditor() | ||
|
||
// // We need to find the first mark of type link in a message and extract the href. | ||
|
||
// const json = editor?.getJSON() | ||
|
||
// const href = useMemo(() => { | ||
// if (!json) return null | ||
|
||
// let firstLink = '' | ||
|
||
// // At every level of the json, we need to find the first mark of type link and extract the href. | ||
// // Once we find the first link, we can stop searching. | ||
// const findFirstLink = (json: any) => { | ||
// if (firstLink) return firstLink | ||
|
||
// if (Array.isArray(json)) { | ||
// for (const item of json) { | ||
// if (typeof item === 'object') { | ||
// findFirstLink(item) | ||
// } | ||
// } | ||
// } else { | ||
// if (json?.type === 'link') { | ||
// const link = json?.attrs?.href | ||
// if (link?.startsWith('mailto')) { | ||
// } else { | ||
// firstLink = json?.attrs?.href | ||
// } | ||
// } else { | ||
// for (const key in json) { | ||
// if (typeof json?.[key] === 'object') { | ||
// findFirstLink(json?.[key]) | ||
// } | ||
// } | ||
// } | ||
// } | ||
// } | ||
|
||
// findFirstLink(json) | ||
|
||
// return firstLink | ||
// }, [json]) | ||
|
||
// // const href = editor?.getAttributes('link').href | ||
|
||
|
||
// const { data, isLoading } = useFrappeGetCall<{ message: LinkPreviewDetails[] }>('raven.api.preview_links.get_preview_link', { | ||
// urls: JSON.stringify([href]) | ||
// }, href ? undefined : null, { | ||
// revalidateIfStale: false, | ||
// revalidateOnFocus: false, | ||
// revalidateOnReconnect: false, | ||
// shouldRetryOnError: false, | ||
// }) | ||
|
||
// if (!href) return null | ||
|
||
// const linkPreview = data?.message?.[0] | ||
|
||
// return <a href={href} target='_blank'> | ||
// <Flex direction='column' gap='2' py='2'> | ||
// {linkPreview ? linkPreview.site_name && linkPreview.description ? <Flex gap='4'> | ||
// {(linkPreview.absolute_image || linkPreview.image) && | ||
// <Box className='relative min-w-[18rem] min-h-[9rem] w-72 h-36'> | ||
// {/* Absolute positioned skeleton loader */} | ||
// <Box className='absolute top-0 z-0 left-0 w-72 h-36' > | ||
// <Box className='animate-pulse bg-gray-3 z-0 w-72 h-36 dark:bg-gray-5 rounded-md'> | ||
|
||
// </Box> | ||
// </Box> | ||
|
||
// <img | ||
// width='100%' | ||
// className='absolute object-cover min-w-[18rem] min-h-[9rem] w-72 h-36 rounded-md z-50 top-0 left-0' | ||
// src={linkPreview.absolute_image || linkPreview.image} | ||
// alt={linkPreview.title} /> | ||
|
||
// </Box> | ||
// } | ||
// <Flex direction='column' gap='1' py='1' className='w-84'> | ||
// <Flex gap='1' direction='column'> | ||
// <Text as='span' weight='bold' size='5' className='cal-sans'>{linkPreview.title}</Text> | ||
// <Text as='span' color='gray' size='2' weight='medium'>{linkPreview.site_name}</Text> | ||
// </Flex> | ||
// <Text as='p' size='2' className='whitespace-break-spaces'>{linkPreview.description}</Text> | ||
// </Flex> | ||
// </Flex> : null : | ||
|
||
// null} | ||
// </Flex> | ||
// </a> | ||
|
||
// }) |
75 changes: 75 additions & 0 deletions
75
mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Mention.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { UserAvatar } from '@/components/common/UserAvatar'; | ||
import { useGetUser } from '@/hooks/useGetUser'; | ||
import { useIsUserActive } from '@/hooks/useIsUserActive'; | ||
import { Flex, HoverCard, Link, Text } from '@radix-ui/themes'; | ||
import Mention from '@tiptap/extension-mention' | ||
import { NodeViewRendererProps, NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react"; | ||
import { BsFillCircleFill } from 'react-icons/bs'; | ||
import { Link as RouterLink } from 'react-router-dom'; | ||
|
||
export const CustomUserMention = Mention.extend({ | ||
name: 'userMention', | ||
addNodeView() { | ||
return ReactNodeViewRenderer(UserMentionRenderer) | ||
} | ||
}) | ||
|
||
export const CustomChannelMention = Mention.extend({ | ||
name: 'channelMention', | ||
addNodeView() { | ||
return ReactNodeViewRenderer(ChannelMentionRenderer) | ||
} | ||
}) | ||
|
||
const UserMentionRenderer = ({ node }: NodeViewRendererProps) => { | ||
|
||
const user = useGetUser(node.attrs.id) | ||
const isActive = useIsUserActive(node.attrs.id) | ||
|
||
return ( | ||
<NodeViewWrapper as={'span'}> | ||
<HoverCard.Root> | ||
<HoverCard.Trigger> | ||
<Link size='2'> | ||
@{user?.full_name ?? node.attrs.label} | ||
</Link> | ||
</HoverCard.Trigger> | ||
<HoverCard.Content> | ||
<Flex gap='2' align='center'> | ||
<UserAvatar src={user?.user_image} alt={user?.full_name ?? node.attrs.label} size='4' /> | ||
<Flex direction='column'> | ||
<Flex gap='3' align='center'> | ||
<Text className='text-gray-12' weight='bold' size='3'>{user?.full_name ?? node.attrs.label}</Text> | ||
{isActive && <Flex gap='1' align='center'> | ||
<BsFillCircleFill className='text-green-400' size='8' /> | ||
<Text className='text-gray-10' size='1'>Online</Text> | ||
</Flex>} | ||
</Flex> | ||
{user && <Text className='text-gray-11' size='1'>{user?.name}</Text>} | ||
</Flex> | ||
</Flex> | ||
|
||
</HoverCard.Content> | ||
</HoverCard.Root> | ||
{/* <Link> | ||
@{node.attrs.label} | ||
</Link> */} | ||
</NodeViewWrapper> | ||
); | ||
}; | ||
|
||
|
||
|
||
const ChannelMentionRenderer = ({ node }: NodeViewRendererProps) => { | ||
|
||
|
||
return ( | ||
<NodeViewWrapper as={'span'}> | ||
<Link asChild> | ||
<RouterLink to={`/channels/${node.attrs.id}`}> | ||
@{node.attrs.label} | ||
</RouterLink> | ||
</Link> | ||
</NodeViewWrapper> | ||
); | ||
}; |
105 changes: 105 additions & 0 deletions
105
...src/components/features/chat-space/chat-view/components/TiptapRenderer/TiptapRenderer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { EditorContent, EditorContext, useEditor } from '@tiptap/react' | ||
import { TextMessage } from '../../../../../../../../types/Messaging/Message' | ||
import { UserFields } from '@/utils/users/UserListProvider' | ||
import { Box, BoxProps } from '@radix-ui/themes' | ||
import Highlight from '@tiptap/extension-highlight' | ||
import StarterKit from '@tiptap/starter-kit' | ||
import css from 'highlight.js/lib/languages/css' | ||
import js from 'highlight.js/lib/languages/javascript' | ||
import ts from 'highlight.js/lib/languages/typescript' | ||
import html from 'highlight.js/lib/languages/xml' | ||
import json from 'highlight.js/lib/languages/json' | ||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight' | ||
import { common, createLowlight } from 'lowlight' | ||
import python from 'highlight.js/lib/languages/python' | ||
import { CustomBlockquote } from './Blockquote' | ||
import { CustomBold } from './Bold' | ||
import { CustomUserMention } from './Mention' | ||
import { CustomLink } from './Link' | ||
import { CustomUnderline } from './Underline' | ||
import { Image } from '@tiptap/extension-image' | ||
import { clsx } from 'clsx' | ||
import Italic from '@tiptap/extension-italic'; | ||
const lowlight = createLowlight(common) | ||
|
||
lowlight.register('html', html) | ||
lowlight.register('css', css) | ||
lowlight.register('js', js) | ||
lowlight.register('ts', ts) | ||
lowlight.register('json', json) | ||
lowlight.register('python', python) | ||
type TiptapRendererProps = BoxProps & { | ||
message: TextMessage, | ||
user?: UserFields, | ||
showLinkPreview?: boolean, | ||
isScrolling?: boolean, | ||
isTruncated?: boolean | ||
} | ||
|
||
export const TiptapRenderer = ({ message, user, isScrolling = false, isTruncated = false, showLinkPreview = true, ...props }: TiptapRendererProps) => { | ||
|
||
const editor = useEditor({ | ||
content: message.text, | ||
editable: false, | ||
editorProps: { | ||
attributes: { | ||
class: isTruncated ? 'line-clamp-3' : '' | ||
} | ||
}, | ||
enableCoreExtensions: true, | ||
extensions: [ | ||
StarterKit.configure({ | ||
heading: false, | ||
codeBlock: false, | ||
bold: false, | ||
blockquote: false, | ||
italic: false, | ||
listItem: { | ||
HTMLAttributes: { | ||
class: 'ml-5 rt-Text text-base' | ||
} | ||
}, | ||
paragraph: { | ||
HTMLAttributes: { | ||
class: 'rt-Text text-base' | ||
} | ||
} | ||
}), | ||
Highlight.configure({ | ||
multicolor: true, | ||
HTMLAttributes: { | ||
class: 'bg-[var(--yellow-6)] dark:bg-[var(--yellow-11)] px-2 py-1' | ||
} | ||
}), | ||
CustomUnderline, | ||
CodeBlockLowlight.configure({ | ||
lowlight | ||
}), | ||
CustomBlockquote, | ||
CustomBold, | ||
CustomUserMention, | ||
CustomLink, | ||
Italic, | ||
Image.configure({ | ||
HTMLAttributes: { | ||
class: 'mt-2 object-cover max-w-[280px]' | ||
}, | ||
inline: true | ||
}), | ||
// TODO: Add channel mention | ||
// CustomChannelMention | ||
] | ||
}) | ||
|
||
return ( | ||
<Box className={clsx('overflow-x-hidden text-ellipsis', props.className)} {...props}> | ||
<EditorContext.Provider value={{ editor }}> | ||
<EditorContent | ||
contentEditable={false} | ||
editor={editor} | ||
readOnly /> | ||
{/* {showLinkPreview && <LinkPreview isScrolling={isScrolling} />} */} | ||
</EditorContext.Provider> | ||
</Box> | ||
) | ||
} |
Oops, something went wrong.