diff --git a/mobile/package.json b/mobile/package.json
index 08778f3d8..eb0af0657 100644
--- a/mobile/package.json
+++ b/mobile/package.json
@@ -21,6 +21,7 @@
"@radix-ui/themes": "^3.0.2",
"@tiptap/extension-code-block-lowlight": "^2.2.3",
"@tiptap/extension-highlight": "^2.2.3",
+ "@tiptap/extension-image": "^2.2.3",
"@tiptap/extension-link": "^2.2.3",
"@tiptap/extension-mention": "^2.2.3",
"@tiptap/extension-placeholder": "^2.2.3",
diff --git a/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx b/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx
index cd2309724..df57a323a 100644
--- a/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx
+++ b/mobile/src/components/features/chat-space/chat-view/MessageBlock.tsx
@@ -1,7 +1,6 @@
import { memo, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { FileMessage, ImageMessage, Message, TextMessage, PollMessage } from '../../../../../../types/Messaging/Message'
import { IonIcon, IonSkeletonText, IonText } from '@ionic/react'
-import { MarkdownRenderer } from '@/components/common/MarkdownRenderer'
import { UserFields } from '@/utils/users/UserListProvider'
import { DateObjectToFormattedDateStringWithoutYear, DateObjectToTimeString } from '@/utils/operations/operations'
import { ChannelMembersContext } from '../ChatInterface'
@@ -22,6 +21,7 @@ import { RavenPoll } from '@/types/RavenMessaging/RavenPoll'
import { RavenPollOption } from '@/types/RavenMessaging/RavenPollOption'
import { MdOutlineBarChart } from 'react-icons/md'
import { ViewPollVotes } from '../../polls/ViewPollVotes'
+import { TiptapRenderer } from './components/TiptapRenderer/TiptapRenderer'
type Props = {
message: Message,
@@ -232,10 +232,7 @@ const MessageContent = ({ message, onReplyMessageClick, onLongPressDisabled, onL
const TextMessageBlock = ({ message, truncate = false }: { message: TextMessage, truncate?: boolean }) => {
-
- return
-
-
+ return
}
const options = {
root: null,
diff --git a/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Blockquote.tsx b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Blockquote.tsx
new file mode 100644
index 000000000..b579d247d
--- /dev/null
+++ b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Blockquote.tsx
@@ -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 (
+
+
+ {node.textContent}
+
+
+ );
+};
\ No newline at end of file
diff --git a/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Bold.tsx b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Bold.tsx
new file mode 100644
index 000000000..120721f45
--- /dev/null
+++ b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Bold.tsx
@@ -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,
+ ];
+ },
+})
\ No newline at end of file
diff --git a/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Link.tsx b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Link.tsx
new file mode 100644
index 000000000..6258607dd
--- /dev/null
+++ b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Link.tsx
@@ -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
+//
+// {linkPreview ? linkPreview.site_name && linkPreview.description ?
+// {(linkPreview.absolute_image || linkPreview.image) &&
+//
+// {/* Absolute positioned skeleton loader */}
+//
+//
+
+//
+//
+
+//
+
+//
+// }
+//
+//
+// {linkPreview.title}
+// {linkPreview.site_name}
+//
+// {linkPreview.description}
+//
+// : null :
+
+// null}
+//
+//
+
+// })
\ No newline at end of file
diff --git a/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Mention.tsx b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Mention.tsx
new file mode 100644
index 000000000..76a110dae
--- /dev/null
+++ b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Mention.tsx
@@ -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 (
+
+
+
+
+ @{user?.full_name ?? node.attrs.label}
+
+
+
+
+
+
+
+ {user?.full_name ?? node.attrs.label}
+ {isActive &&
+
+ Online
+ }
+
+ {user && {user?.name}}
+
+
+
+
+
+ {/*
+ @{node.attrs.label}
+ */}
+
+ );
+};
+
+
+
+const ChannelMentionRenderer = ({ node }: NodeViewRendererProps) => {
+
+
+ return (
+
+
+
+ @{node.attrs.label}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/TiptapRenderer.tsx b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/TiptapRenderer.tsx
new file mode 100644
index 000000000..d8e85123d
--- /dev/null
+++ b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/TiptapRenderer.tsx
@@ -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 (
+
+
+
+ {/* {showLinkPreview && } */}
+
+
+ )
+}
\ No newline at end of file
diff --git a/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Underline.tsx b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Underline.tsx
new file mode 100644
index 000000000..a4da9fa7e
--- /dev/null
+++ b/mobile/src/components/features/chat-space/chat-view/components/TiptapRenderer/Underline.tsx
@@ -0,0 +1,15 @@
+import TiptapUnderline from '@tiptap/extension-underline'
+import { mergeAttributes } from "@tiptap/react";
+
+export const CustomUnderline = TiptapUnderline.extend({
+ renderHTML({ HTMLAttributes }) {
+ return [
+ "span",
+ mergeAttributes(HTMLAttributes, {
+ class: 'rt-Text rt-reset rt-Link rt-underline-always text-gray-12',
+ 'data-accent-color': 'gray',
+ }), // mergeAttributes is a exported function from @tiptap/core
+ 0,
+ ];
+ },
+})
\ No newline at end of file
diff --git a/mobile/yarn.lock b/mobile/yarn.lock
index bc2a55aed..56609c796 100644
--- a/mobile/yarn.lock
+++ b/mobile/yarn.lock
@@ -2627,6 +2627,11 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.3.0.tgz#e4934b32c005b1cf0f03fab5f4caff8cacbf4c7e"
integrity sha512-4DB8GU3uuDzzyqUmONIb3CHXcQ6Nuy4mHHkFSmUyEjg1i5eMQU5H7S6mNvZbltcJB2ImgCSwSMlj1kVN3MLIPg==
+"@tiptap/extension-image@^2.2.3":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.3.0.tgz#914c4c5030fa4278f53e808c03819f7422da4497"
+ integrity sha512-v1fLEEzrfXWavsLFUEkTiYYxwm1WDNrjuUriU5tG2Jv22NL1BL4BLVbZbGdkAk+qHWy8QWszrDJbcgGh2VNCoQ==
+
"@tiptap/extension-italic@^2.3.0":
version "2.3.0"
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.3.0.tgz#72279632a2652642abed2078a920e2e05aff030e"
diff --git a/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/List.tsx b/raven-app/src/components/feature/chat/ChatMessage/Renderers/TiptapRenderer/List.tsx
deleted file mode 100644
index e69de29bb..000000000