Skip to content

Commit

Permalink
feat: added tiptap on webapp (#497)
Browse files Browse the repository at this point in the history
* wip: tiptap integration

* fix: styling for tiptap editor and channel mention

* feat: added tiptap on web app

* feat: added styling for links in tiptap

* feat: blockquote styling in editor

* fix: highlight color of mentions

* fix: added styles on renderer

* fix: blockquote stylesx
  • Loading branch information
nikkothari22 authored Oct 22, 2023
1 parent 8e328bb commit 1c39bb5
Show file tree
Hide file tree
Showing 29 changed files with 2,223 additions and 154 deletions.
31 changes: 31 additions & 0 deletions mobile/src/types/RavenMessaging/RavenMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

export interface RavenMessage{
creation: string
name: string
modified: string
owner: string
modified_by: string
docstatus: 0 | 1 | 2
parent?: string
parentfield?: string
parenttype?: string
idx?: number
/** Channel ID : Link - Raven Channel */
channel_id: string
/** Text : Long Text */
text?: string
/** JSON : JSON */
json?: any
/** File : Attach */
file?: string
/** File Thumbnail : Attach */
file_thumbnail?: string
/** Message Type : Select */
message_type?: "Text" | "Image" | "File"
/** Message Reactions : JSON */
message_reactions?: any
/** Is Reply : Check */
is_reply?: 0 | 1
/** Linked Message : Link - Raven Message */
linked_message?: string
}
19 changes: 17 additions & 2 deletions raven-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,28 @@
"@chakra-ui/react": "^2.5.5",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.5",
"@tiptap/extension-code-block-lowlight": "^2.1.12",
"@tiptap/extension-highlight": "^2.1.12",
"@tiptap/extension-link": "^2.1.12",
"@tiptap/extension-mention": "^2.1.12",
"@tiptap/extension-placeholder": "^2.1.12",
"@tiptap/extension-typography": "^2.1.12",
"@tiptap/extension-underline": "^2.1.12",
"@tiptap/pm": "^2.1.12",
"@tiptap/react": "^2.1.12",
"@tiptap/starter-kit": "^2.1.12",
"@tiptap/suggestion": "^2.1.12",
"@types/js-cookie": "^3.0.3",
"@types/unist": "^2.0.6",
"chakra-react-select": "^4.6.0",
"cmdk": "^0.2.0",
"emoji-picker-element": "^1.18.4",
"emoji-picker-react": "^4.4.7",
"framer-motion": "^9.0.2",
"frappe-react-sdk": "^1.3.7",
"highlight.js": "^11.9.0",
"js-cookie": "^3.0.5",
"lowlight": "^3.1.0",
"quill-image-drop-and-paste": "^1.3.0",
"quill-linkify": "^0.3.0",
"quill-mention": "^4.0.0",
Expand All @@ -38,7 +52,8 @@
"react-virtuoso": "^4.3.8",
"rehype-raw": "^6.1.1",
"remark-gfm": "^3.0.1",
"timeago-react": "^3.0.5"
"timeago-react": "^3.0.5",
"tippy.js": "^6.3.7"
},
"devDependencies": {
"@types/react": "^18.0.27",
Expand All @@ -47,4 +62,4 @@
"typescript": "^4.9.3",
"vite": "^4.1.0"
}
}
}
25 changes: 25 additions & 0 deletions raven-app/src/components/common/EmojiPicker/EmojiPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createElement, useEffect, useRef } from "react"
import 'emoji-picker-element'
import { useColorModeValue } from "@chakra-ui/react"
import './emojiPicker.styles.css'

export const EmojiPicker = ({ onSelect }: { onSelect: (emoji: string) => void }) => {

const className = useColorModeValue('light', 'dark')
const ref = useRef<any>(null)

useEffect(() => {

const handler = (event: any) => {
onSelect(event.detail.unicode)
}
ref.current?.addEventListener('emoji-click', handler)
ref.current.skinToneEmoji = '👍'

return () => {
ref.current?.removeEventListener('emoji-click', handler)
}
}, [])

return createElement('emoji-picker', { ref, class: className })
}
23 changes: 23 additions & 0 deletions raven-app/src/components/common/EmojiPicker/emojiPicker.styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
emoji-picker {
--indicator-color: var(--chakra-colors-blue-500);
--input-padding: var(--chakra-space-2);
--input-font-size: var(--chakra-fontSizes-sm);
}

.picker {
border-radius: 10px;
}

emoji-picker.light {
--background: var(--chakra-colors-gray-50);
--border-color: var(--chakra-colors-gray-50);
--button-active-background: var(--chakra-colors-gray-200);
--button-hover-background: var(--chakra-colors-gray-200);
}

emoji-picker.dark {
--background: var(--chakra-colors-gray-800);
--border-color: var(--chakra-colors-gray-900);
--button-active-background: var(--chakra-colors-gray-600);
--button-hover-background: var(--chakra-colors-gray-600);
}
14 changes: 14 additions & 0 deletions raven-app/src/components/common/TooltipButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Button, ButtonProps, IconButton, IconButtonProps } from "@chakra-ui/react";
import { forwardRef } from "react";

export const TooltipButton = forwardRef(({ children, ...rest }: ButtonProps, ref) => (
<Button ref={ref} {...rest}>
{children}
</Button>
))

export const TooltipIconButton = forwardRef(({ children, ...rest }: IconButtonProps, ref) => (
<IconButton ref={ref} {...rest}>
{children}
</IconButton>
))
110 changes: 110 additions & 0 deletions raven-app/src/components/feature/chat/ChatInput/ChannelMentionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// import './MentionList.scss'

import { useIsUserActive } from '@/hooks/useIsUserActive'
import { ChannelListItem } from '@/utils/channel/ChannelListProvider'
import { getChannelIcon } from '@/utils/layout/channelIcon'
import { UserFields } from '@/utils/users/UserListProvider'
import { Avatar, AvatarBadge, Button, HStack, Icon, Stack, StackDivider, Text, useColorModeValue } from '@chakra-ui/react'
import { ReactRendererOptions } from '@tiptap/react'
import {
forwardRef, useEffect, useImperativeHandle,
useState,
} from 'react'

export default forwardRef((props: ReactRendererOptions['props'], ref) => {

const buttonGroupBgColor = useColorModeValue('white', 'gray.900')
const [selectedIndex, setSelectedIndex] = useState(0)

const selectItem = (index: number) => {
const item = props?.items[index]
if (item) {
props.command({ id: item.name, label: item.channel_name })
}
}

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 }: { event: KeyboardEvent }) => {
if (event.key === 'ArrowUp') {
upHandler()
return true
}

if (event.key === 'ArrowDown') {
downHandler()
return true
}

if (event.key === 'Enter') {
enterHandler()
return true
}

return false
},
}))

return (
<Stack divider={<StackDivider />} spacing='0' rounded='md' shadow='md' bgColor={buttonGroupBgColor}>
{props?.items.length
? props.items.map((item: ChannelListItem, index: number) => (
<MentionItem
item={item}
index={index}
selectItem={selectItem}
selectedIndex={selectedIndex}
key={item.name}
itemsLength={props.items.length}
/>
))
: <div className="item">No result</div>
}
</Stack>
)
})

const MentionItem = ({ item, index, selectItem, selectedIndex, itemsLength }: { itemsLength: number, selectedIndex: number, index: number, item: ChannelListItem, selectItem: (index: number) => void }) => {

const { selectedBgColor, selectedColor, textColor, backgroundColor } = useColorModeValue({
selectedBgColor: 'gray.900',
selectedColor: 'white',
textColor: 'gray.900',
backgroundColor: 'white'
}, {
selectedBgColor: 'gray.100',
selectedColor: 'gray.900',
textColor: 'gray.100',
backgroundColor: 'gray.800'
})
return <HStack
as={'button'}
rounded='md'
roundedBottom={index === itemsLength - 1 ? 'md' : 'none'}
roundedTop={index === 0 ? 'md' : 'none'}
px='3'
py='1.5'
textAlign={'left'}
// colorScheme='blue'
bgColor={index === selectedIndex ? selectedBgColor : backgroundColor}
color={index === selectedIndex ? selectedColor : textColor}
key={index}
onClick={() => selectItem(index)}
>
<Icon as={getChannelIcon(item.type)} />
<Text as='span' fontSize='sm'>{item.channel_name}</Text>
</HStack>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { CustomFile } from '@/components/feature/file-upload/FileDrop'
import { useRef, useState } from 'react'
import { Message } from '../../../../../../../types/Messaging/Message'
import { useFrappeCreateDoc, useFrappeFileUpload, useFrappeUpdateDoc } from 'frappe-react-sdk'
import { getFileExtension } from '@/utils/operations'


export const fileExt = ['jpg', 'JPG', 'jpeg', 'JPEG', 'png', 'PNG', 'gif', 'GIF']

export default function useFileUpload(channelID: string, selectedMessage?: Message | null) {

const fileInputRef = useRef<any>(null)

const [files, setFiles] = useState<CustomFile[]>([])

const { createDoc, loading: creatingDoc, error: errorCreatingDoc, reset: resetCreateDoc } = useFrappeCreateDoc()
const { upload, loading: uploadingFile, progress, error: errorUploadingDoc, reset: resetUploadDoc } = useFrappeFileUpload()
const { updateDoc, loading: updatingDoc, error: errorUpdatingDoc, reset: resetUpdateDoc } = useFrappeUpdateDoc()

const addFile = (file: File) => {

const newFile: CustomFile = file as CustomFile
if (newFile) {
newFile.fileID = file.name + Date.now()
newFile.uploadProgress = 0
setFiles((f: any) => [...f, newFile])
}
}
const removeFile = (id: string) => {
let newFiles = files.filter(file => file.fileID !== id)
setFiles(newFiles)
}

const uploadFiles = async () => {

if (files.length > 0) {
const promises = files.map(async (f: CustomFile) => {
let docname = ''
return createDoc('Raven Message', {
channel_id: channelID
}).then((d) => {
docname = d.name
f.uploading = true
f.uploadProgress = progress
return upload(f, {
isPrivate: true,
doctype: 'Raven Message',
docname: d.name,
fieldname: 'file',
})
}).then((r) => {
f.uploading = false
return updateDoc("Raven Message", docname, {
file: r.file_url,
message_type: fileExt.includes(getFileExtension(f.name)) ? "Image" : "File",
})
})
})

return Promise.all(promises)
.then(() => {
setFiles([])
resetCreateDoc()
resetUploadDoc()
resetUpdateDoc()
}).catch((e) => {
console.log(e)
})
} else {
return Promise.resolve()
}
}

return {
fileInputRef,
files,
setFiles,
removeFile,
addFile,
uploadFiles
}
}
Loading

0 comments on commit 1c39bb5

Please sign in to comment.