Quellcode durchsuchen

fixed msg render, added msg editing

Ivar vor 3 Jahren
Ursprung
Commit
0b84b48686

+ 4 - 5
src/App.js

@@ -1,4 +1,4 @@
-import React, {useState, useEffect, useRef, Component} from 'react'
+import React, {useState, useEffect, useRef} from 'react'
 import './App.scss'
 import {Provider, connect} from 'react-redux'
 import {Router, Route, Link, Redirect, Switch} from 'react-router-dom'
@@ -9,8 +9,6 @@ import {
 } from "./reducers"
 import {
   actionFullChatList,
-
-  actionUpdateChat
 } from "./actions"
 
 import {
@@ -81,14 +79,15 @@ const history = createHistory()
 
 function App() {
 
-  // store.dispatch(actionUpdateChat('NewCHAT', [{_id: '617ad9262b5f0a03e6fd4037'}]))
+  window.addEventListener("contextmenu", e => e.preventDefault());
 
   return (
     <Router history={history}>
       <Provider store={store}>
           <div className="App">
                                 
-            <CAuthSwitch />
+
+           <CAuthSwitch />
           
           </div>
         </Provider>

+ 2 - 26
src/actions/authActions.js

@@ -42,9 +42,9 @@ import {
       )
    )
 
-   export const actionFullRegister = (login, password) => (
+   export const actionFullRegister = (login, password, nick) => (
       async (dispatch) => {
-         let regId = await dispatch(actionRegister(login, password))
+         let regId = await dispatch(actionRegister(login, password, nick))
          if (regId) {
             await dispatch(actionFullLogin(login, password))
          }
@@ -114,30 +114,6 @@ import {
    )
 
 
-   
-   // происходит когда юзер уходит сам, иначе в чат добавляются юзер, а не наоборот
-   const actionUpdateUserCahts = (userId, newChats) => (
-      actionPromise('updateUser', gql(`mutation updateUser($user:UserInput) {
-         UserUpsert(user:$user) {
-            _id   
-            login  
-            nick
-         }
-      }`, { user: {_id: userId, chats: newChats } }))
-   )
-
-   export const setUserChats = (chatId) => (
-      async (dispatch, getState) => {
-         let { promise } = getState()
-         let userId = promise.myProfile.payload._id
-         let oldChats = promise.myProfile.payload.chats
-         let newChats = oldChats.filter((chat) => chat._id !== chatId)
-         await dispatch(actionUpdateUserCahts(userId, newChats))
-         await dispatch(actionAboutMe()) 
-      }
-   )
-
-
       // не работает
    // export const actionRemoveUser = (userId) => (
    //    actionPromise('changeUser', gql(`mutation changeUser($user: UserInput) {

+ 31 - 1
src/actions/chatsActions.js

@@ -4,6 +4,7 @@ import {
    actionChatList,
    actionChatOne,
    actionChatLeft,
+   actionAboutMe
 } from '../reducers'
 import { actionGetAllLastMsg } from './msgActions'
 import { actionUploadFile } from './mediaActions'
@@ -124,6 +125,7 @@ export const actionFullChatList = (userId, currentCount, limitCount=50) => (
       let payload = await dispatch(actionGetChatsByUser(userId, currentCount, limitCount))
       if (payload) {
          await dispatch(actionChatList(payload))
+
          await dispatch(actionGetAllLastMsg(payload))
       }
    }
@@ -169,7 +171,6 @@ export const actionGetChatById = (chatId) => (
 
 
 
-
 export const actionChatsCount = (userId) => (
    actionPromise('chatsCount', gql(`query chatsCount($q: String) {
       ChatCount (query: $q)  
@@ -182,6 +183,35 @@ export const actionChatsCount = (userId) => (
 
 
 
+// происходит когда юзер уходит сам, иначе в чат добавляются юзер, а не наоборот
+const actionUpdateUserChats = (userId, newChats) => (
+   actionPromise('updateUserChats', gql(`mutation updateUserChats($user:UserInput) {
+      UserUpsert(user:$user) {
+         _id   
+         login  
+         nick
+         chats {
+            _id
+         }
+      }
+   }`, { user: {_id: userId, chats: newChats } }))
+)
+
+export const removeUserChat = (chatId) => (
+   async (dispatch, getState) => {
+      let { promise } = getState()
+      let userId = promise.myProfile.payload._id
+      let oldChats = promise.myProfile.payload.chats
+      let newChats = oldChats.filter((chat) => chat._id !== chatId)
+      await dispatch(actionUpdateUserChats(userId, newChats))
+      let chat = await dispatch(actionGetChatById(chatId))
+      await dispatch(actionChatOne(chat))
+      await dispatch(actionAboutMe()) 
+   }
+)
+
+
+
 
 
 

+ 4 - 4
src/actions/index.js

@@ -5,7 +5,6 @@ import {
    actionFullRegister, 
    actionSetUserInfo,
    actionSetUserPass,
-   setUserChats
 } from './authActions'
 import {
    actionGetChatsByUser,
@@ -13,7 +12,8 @@ import {
 
    actionSetChatInfo,
    actionFullChatList,
-   actionChatsCount
+   actionChatsCount,
+   removeUserChat
 } from './chatsActions'
 import {
    actionGetMsgsByChat,
@@ -39,7 +39,6 @@ export {
    actionFullRegister, 
    actionSetUserInfo,
    actionSetUserPass,
-   setUserChats
 } 
 export {
    actionGetChatsByUser,
@@ -47,7 +46,8 @@ export {
 
    actionSetChatInfo,
    actionFullChatList,
-   actionChatsCount
+   actionChatsCount,
+   removeUserChat
 }
 export {
    actionGetMsgsByChat,

+ 31 - 51
src/actions/msgActions.js

@@ -66,66 +66,32 @@ export const actionGetMsgsByChat = (chatId, skipCount=0, limitCount=50) => (
 
 export const actionFullMsgsByChat = (chatId, currentCount, limitCount=50) => (
    async (dispatch, getState) => {
-      console.log('MSG STATE', getState())
 
-      console.log(getState().chats[chatId]?.messages?.length, currentCount + limitCount)
-      // TODO если длинна массива равна общему количеству то тоже не выполнять
-      // +2 так как затягиваем одно сообщение при загрузке всех чатов
-      if ( (getState().chats[chatId]?.messages?.length ?? 0) < currentCount + limitCount) {
+      let chat = getState().chats[chatId]
+
+      if (!chat || (chat.messages && chat.messages[0]?._id !== chat.firstMsgId) ) {
+         console.log(chat)
 
          let payload = await dispatch(actionGetMsgsByChat(chatId, currentCount, limitCount))
          if (payload) {
             await dispatch(actionMsgList(payload))
          }
-      }
+      } 
+
    }
 )
 
 
 
-
-const actionLastMsgByChat = (chatId) => (
-   actionPromise('lastMsg', gql(`query lastMsg($q: String) {
+const actionFirstMsgByChat = (chatId) => (
+   actionPromise('firstMsg', gql(`query firstMsg($q: String) {
       MessageFind (query: $q){
          _id
-         createdAt
-         owner {
-            _id
-            login
-            nick
-            avatar {
-               url
-            }
-         }
-         text
-         chat {
-            _id
-         }
-         media {
-            _id
-            url
-            originalFileName
-            type
-         }
-
-         forwardWith {
-            _id
-         }
-         replies {
-            _id
-         }
-
-         replyTo {
-            _id
-         }
-         forwarded {
-            _id
-         }
       }     
    }`, { 
          q: JSON.stringify([  { "chat._id": chatId },
                               { 
-                                 sort: [{_id: -1}],
+                                 sort: [{_id: 1}],
                                  skip: [0], 
                                  limit: [1] 
                               } 
@@ -134,13 +100,18 @@ const actionLastMsgByChat = (chatId) => (
    ))
 )
 
+
 export const actionGetAllLastMsg = (chats) => (
    async (dispatch) => {
 
-      let msgReq = chats.map((chat) => dispatch(actionLastMsgByChat(chat._id)))
-
-      for await (const [msg] of msgReq) {
-         msg && dispatch(actionMsgOne(msg))
+      let msgReq = chats.map((chat) => Promise.all(
+                                       [dispatch(actionGetMsgsByChat(chat._id, 0, 1)),
+                                       dispatch(actionFirstMsgByChat(chat._id)) ]) )
+                                       
+      for await (const [lastMsgs, firstMsgs] of msgReq) {
+         lastMsgs.length && dispatch(actionMsgOne(lastMsgs[0]))
+         firstMsgs.length && dispatch(actionChatOne({ _id: lastMsgs[0].chat._id,
+                                                      firstMsgId: firstMsgs[0]._id}))
       }
    }
 )
@@ -148,7 +119,6 @@ export const actionGetAllLastMsg = (chats) => (
 
 
 
-
 export const actionMsgsCount = (chatId) => (
    actionPromise('msgsCount', gql(`query msgsCount($q: String) {
       MessageCount (query: $q)  
@@ -256,16 +226,26 @@ export const actionSendMsg = (chatId, text, inputName, files, msgId) => (
    async (dispatch) => {
 // тут нужно отделить уже залитые файлы от тех которые лежат локально
 // локальные залить и получить ид, с залитых просто получить ид
+      const mediaToUpload = []
       const media = []
       for (const file of files) {
-         if (file.preview.match(/blob/)) {
-            let fileObj = await dispatch(actionUploadFile(inputName, file))
-            fileObj && media.push({_id: fileObj?._id})
+         if (file.url.match(/blob/)) {
+
+            mediaToUpload.push(dispatch(actionUploadFile(inputName, file)))
          } else {
+
             let fileObj = file
             media.push({_id: fileObj?._id})
          }
       }
+
+      let fileArr = await Promise.all(mediaToUpload)
+      if (fileArr) {
+         for (const uploadedFile of fileArr) {
+            media.push({_id: uploadedFile?._id})
+         }
+      }
+
       let payload = await dispatch(actionUpdateMsg(chatId, text, media, msgId))
       // if (payload) {
       //    await dispatch(actionMsgOne(payload))

+ 9 - 5
src/components/ChatList.jsx

@@ -10,13 +10,17 @@ import { connect }  from 'react-redux'
 
 const Chat = ({ chat, currChat }) => {
 
-  const [msgText, setMsgText] = useState( chat.messages && chat.messages[0]?.text || 'нема')
+  // const [newMsgCount, setNewMsgCount] = useState(chat.messages &&
+  //                    (chat.messages.filter(msg => msg.createdAt > chat.lastVizited)).length )
+  const [msgText, setMsgText] = useState(chat.messages && 
+                    chat.messages[chat.messages.length - 1]?.text || '...')
 
-  // const {_id, title, owner, members, messages} = chat
-  // const { text, owner: msgOwner, media, createdAt } = messages
 
   useEffect(() => {
-    setMsgText( chat.messages && chat.messages[0]?.text || '...')
+    // setNewMsgCount( chat.messages &&
+    //         (chat.messages.filter(msg => msg.createdAt > chat.lastVizited)).length )
+    setMsgText( chat.messages && 
+            chat.messages[chat.messages.length - 1]?.text || '...' )
   }, [chat])
 
   return (
@@ -52,7 +56,7 @@ const Chat = ({ chat, currChat }) => {
             </div> 
 
             <div className={"chatMsgCount"}>
-            
+              {/* {newMsgCount} */}
             </div> 
           </div> 
 

+ 50 - 5
src/components/ChatMngHeader.jsx

@@ -2,10 +2,13 @@ import React, {useState, useEffect, useRef} from 'react'
 import IconButton from '@mui/material/IconButton';
 import ArrowBackIcon from '@mui/icons-material/ArrowBack';
 import MoreVertIcon from '@mui/icons-material/MoreVert';
+import Menu from '@mui/material/Menu';
+import MenuItem from '@mui/material/MenuItem';
 
 import { ChatAvatar, CChatModal } from "../components"
 
 import { printEnding } from "../helpers"
+import { removeUserChat } from "../actions"
 import { connect } from 'react-redux'
 
 
@@ -88,11 +91,53 @@ const chatMinInfo = ({ chat, OPEN }) => {
 }
 
 
-const ChatMngHeader = ({ chats, chatId }) => {
 
 
-   const chat = chats[chatId]
+const ChatMenu = ({ chatId, onLeave }) => {
+   const [anchorEl, setAnchorEl] = useState(null)
+   const open = !!anchorEl
+
+   const handleClick = (event) => {
+     setAnchorEl(event.currentTarget)
+   }
+
+   const handleClose = () => {
+     setAnchorEl(null)
+   }
+
+   return (
+      <>
+         <IconButton 
+               style={{ color: '#fff' }} 
+               onClick={handleClick}>
+            <MoreVertIcon />
+         </IconButton>
+   
+         <Menu
+            anchorEl={anchorEl}
+            open={open}
+            onClose={handleClose}
+         >
+
+            <MenuItem onClick={handleClose}>
+               Что то сделать
+            </MenuItem>
+
+            <MenuItem onClick={() => onLeave(chatId)}>
+               Покинуть чат
+            </MenuItem>
+                                    
+         </Menu>
+      </>
+   ) 
+}
+const CChatMenu = connect(null, {onLeave: removeUserChat})(ChatMenu)
 
+
+
+const ChatMngHeader = ({ chats, chatId }) => {
+
+   const chat = chats[chatId]
    // console.log(chat)
    return (
       <div style={chatMngBody}>
@@ -108,11 +153,11 @@ const ChatMngHeader = ({ chats, chatId }) => {
          
 
          <div style={blockRight}>
-            <IconButton style={{ color: '#fff' }} >
-               <MoreVertIcon />
-            </IconButton>
+            <CChatMenu chatId={chatId} />
          </div>
       </div>
    )
 }
 export const CChatMngHeader = connect(state => ( { chats: state.chats || []} ))(ChatMngHeader)
+
+

+ 0 - 1
src/components/ChatModal.jsx

@@ -162,7 +162,6 @@ const ChatModal = ({ minTitle="2", chat, onСonfirm, titleError, myProfile, crea
                      </Typography>
                      <TextField            
                         onChange={(e) => {
-                              e.target.value = e.target.value.trim()
                               setTitle(e.target.value)
                            }
                         }     

+ 1 - 57
src/components/MainMenu.jsx

@@ -1,8 +1,6 @@
 import React, {useState} from 'react'
 import IconButton from '@mui/material/IconButton';
 import MenuIcon from '@mui/icons-material/Menu';
-import Menu from '@mui/material/Menu';
-import MenuItem from '@mui/material/MenuItem';
 
 import Drawer from '@mui/material/Drawer';
 import Box from '@mui/material/Box';
@@ -27,56 +25,6 @@ const LogoutBtn = ({onLogout}) => (
 )
 const CLogoutBtn = connect(null, {onLogout: actionFullLogout})(LogoutBtn)
 
-export const MainMenu = () => {
-   const [anchorEl, setAnchorEl] = useState(null)
-   const open = !!anchorEl
-
-   const handleClick = (event) => {
-     setAnchorEl(event.currentTarget)
-   }
-
-   const handleClose = () => {
-     setAnchorEl(null)
-   }
-
-   return (
-      <>
-         <IconButton edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}
-            id="basic-button"
-            aria-controls={open ? 'basic-menu' : undefined}
-            aria-haspopup="true"
-            aria-expanded={open ? 'true' : undefined}
-            onClick={handleClick}
-         >
-            <MenuIcon />
-         </IconButton>
-   
-         <Menu
-         id="basic-menu"
-         anchorEl={anchorEl}
-         open={open}
-         onClose={handleClose}
-         MenuListProps={{
-         'aria-labelledby': 'basic-button',
-         }}
-         >
-
-            <CProfileModal />
-
-            <MenuItem onClick={handleClose}>
-               Контакты
-            </MenuItem>
-
-            <CLogoutBtn />
-                                    
-         </Menu>
-      </>
-   ) 
-}
-
-
-
-
 
 const AboutMe = ({ myProfile }) => {
    const {login, nick} = myProfile
@@ -130,11 +78,7 @@ export const MenuDrawer = ({ }) => {
 
    return (
      <div>
-         <IconButton edge="start" color="inherit" aria-label="menu" sx={{ mr: 1 }}
-            id="basic-button"
-            aria-controls={open ? 'basic-menu' : undefined}
-            aria-haspopup="true"
-            aria-expanded={open ? 'true' : undefined}
+         <IconButton edge="start" color="inherit" sx={{ mr: 1 }}
             onClick={toggleDrawer(true)}
             >
             

+ 88 - 53
src/components/Msg.jsx

@@ -1,10 +1,12 @@
 import React, {useState, useEffect, useRef} from 'react'
+import ReactDOM from "react-dom";
 import FileDownloadIcon from '@mui/icons-material/FileDownload';
 import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
-import Popper from '@mui/material/Popper';
-
+import Popover from '@mui/material/Popover';
+import List from '@mui/material/List';
+import ListItemButton from '@mui/material/ListItemButton';
 import { connect } from 'react-redux'
-import { dateFromStamp, stringColor, backURL } from '../helpers'
+import { dateFromStamp, stringColor, backURL, decorateLinks } from '../helpers'
 
 import { UserAvatar, CMyAvatar } from '.'
 
@@ -15,8 +17,9 @@ const msgBlock = {
    justifyContent: "flex-start",
    alignItems: "start",
    margin: "2px",
+   marginLeft: "10px",
    marginRight: "5%",
-   maxWidth: "calc(95% - 2px)",
+   maxWidth: "calc(95% - 10px)",
    minWidth: "40%",
    wordWrap: "break-word",
 }
@@ -27,8 +30,9 @@ const myMsgBlock = {
    justifyContent: "flex-start",
    alignItems: "start",
    margin: "2px",
+   marginRight: "10px",
    marginLeft: "5%",
-   maxWidth: "calc(95% - 2px)",
+   maxWidth: "calc(95% - 10px)",
    minWidth: "40%",
    wordWrap: "break-word",
 }
@@ -123,14 +127,14 @@ const textDownload = {
 }
 
 
-const Msg = ({ msg, prevOwner, prevTime, myProfile, onEvent }) => {
+const Msg = ({ msg, myProfile, onEdit }) => {
 
    const myId = myProfile?._id || null
    const myLogin = myProfile?.login || null
    const myNick = myProfile?.nick || null
 
-   const { _id, text, owner, media, createdAt } = msg
-   const {nick, login, avatar} = owner
+   const { _id, text, owner, media, createdAt, nextOwner, nextTime } = msg
+   const { nick, login, avatar } = owner
 
    const allMedia = {}
    if (media) for (const file of media) {
@@ -145,28 +149,87 @@ const Msg = ({ msg, prevOwner, prevTime, myProfile, onEvent }) => {
          }
       }  
    }
+   // console.log(allMedia)
 
-   const prOwner = prevOwner.current
-   prevOwner.current = owner._id
 
-   const prTime = prevTime.current
-   prevTime.current = createdAt
+   const [anchorEl, setAnchorEl] = useState(null)
+   // const [top, setTop] = useState(0)
+   // const [left, setLeft] = useState(0)
+ 
+   const handleClick = (e) => {
+      // e.preventDefault()
+      // console.log(e)
+      // if (e.button === 2) {
+      //    console.log('dasdadasdas')
+      // }
+      // console.log(e.pageX - e.target.offsetLeft, e.pageY - e.target.offsetTop)
+      // console.log(e.target.offsetTop, e.target.offsetLeft)
+
+      // setTop(e.clientY)
+      // setLeft(e.clientX)
+      setAnchorEl(e.currentTarget)
+
+   }
+
+   const handleClose = () => {
+      setAnchorEl(null)
+   }
+ 
+   const open = Boolean(anchorEl);
+ 
 
-   // console.log( prTime - createdAt,  text)
    const nameBlockNew = {...nameBlock, color: stringColor.stringToColor(nick || login)}
    return (
-         <div onClick={onEvent}
-            style={(myId === owner._id) ? 
-               ( (prOwner === owner._id) ? 
-                  {...myMsgBlock, marginBottom: "2px"} : {...myMsgBlock, marginBottom: "15px"}) : 
-                  ( (prOwner === owner._id) ? 
-                     {...msgBlock, marginBottom: "2px"} : {...msgBlock, marginBottom: "15px"})
+         <>
+          <Popover
+            open={open}
+            anchorEl={anchorEl}
+            onClose={handleClose}
+            anchorOrigin={{
+              vertical: 'bottom',
+              horizontal: 'left',
+            }}
+            // anchorPosition={{
+            //    top: 300,
+            //    left: 300
+            // }}
+         >
+
+
+                                    
+         <List sx={{ width: '100%', maxWidth: 300, bgcolor: 'background.paper' }}>
+
+            <ListItemButton onClick={() => {
+               handleClose()
+            }}>
+               Ответить
+            </ListItemButton>
+
+            <ListItemButton onClick={() => {
+               onEdit(msg);
+               handleClose()
+            }} >
+               Редактировать
+            </ListItemButton>
+
+         </List>
+
+
+       </Popover>
+
+         <div onContextMenu={handleClick}
+
+            style={  (myId === owner._id) ? 
+                        ( (nextOwner === owner._id) ? 
+                           {...myMsgBlock, marginBottom: "2px"} : {...myMsgBlock, marginBottom: "15px"}) : 
+                              ( (nextOwner === owner._id) ? 
+                                 {...msgBlock, marginBottom: "2px"} : {...msgBlock, marginBottom: "15px"})
                }
-            >
+         >
 
             <div style={avBlock} >
-               { (prOwner === owner._id && 
-                     prTime - createdAt < 600000) || 
+               { (nextOwner === owner._id && 
+                     nextTime - createdAt < 600000) || 
                            ( (myId === owner._id) ? 
                                  <CMyAvatar /> : 
                                        <UserAvatar profile={owner} /> ) }
@@ -186,10 +249,11 @@ const Msg = ({ msg, prevOwner, prevTime, myProfile, onEvent }) => {
                               {
                                  (key === 'image') &&
                                        allMedia[key].map((mediaObj) => 
-                                                                     <img 
+                                                                     <img
                                                                         key={mediaObj.url}
                                                                         style={imgStyle}
                                                                         src={backURL + mediaObj.url} 
+                                                                        type={mediaObj.type}
                                                                      />
                                                                                        ) ||
                                     (key === 'video') &&
@@ -253,6 +317,7 @@ const Msg = ({ msg, prevOwner, prevTime, myProfile, onEvent }) => {
                </div>
             </div>
          </div> 
+      </>
    )
 }
 export const CMsg = connect( state => ({ myProfile: state.promise.myProfile?.payload || {}}))(Msg)
@@ -260,36 +325,6 @@ export const CMsg = connect( state => ({ myProfile: state.promise.myProfile?.pay
 
 
 
-export const MsgMenu = (props) => {
-
-   const [anchorEl, setAnchorEl] = useState(null);
- 
-   const handleClick = (e) => {
-      e.preventDefault()
-      if (e.button === 2) {
-
-      }
-      setAnchorEl(anchorEl ? null : e.currentTarget);
-
-      
-   }
- 
-   const open = Boolean(anchorEl);
- 
-   return (
-     <div>
-
-      <CMsg {...props} onEvent={handleClick} /> 
-
-       <Popper open={open} anchorEl={anchorEl}>
-
-         <div style={{ backgroundColor: "#fff"}} >
-            РЕДАКТИРОВАНИЕ
-         </div>
-       </Popper>
-     </div>
-   )
- }
 
 
 

+ 8 - 17
src/components/MsgList.jsx

@@ -6,38 +6,29 @@ import { CMsg, MsgMenu } from '.'
 
 const msgsWrapper = {
    display: "flex",
-   flexDirection: "column-reverse",
+   flexDirection: "column",
    justifyContent: "flex-start",
    alignItems: "stretch",
    paddingTop: "10px",
    paddingBottom: "5px"
 }
 
-const MsgList = ({chats={}, chatId }) => {
+const MsgList = ({chats={}, chatId, onEdit }) => {
 
-   const prevOwner = useRef('')
-   const prevTime = useRef('')
-
-   useEffect(() => {
-      prevOwner.current = ''
-      prevTime.current = ''
-   })
-
-   // надо ли писать реф?
    const msgArr = chats[chatId]?.messages
 
    return (
       <div 
          style={msgsWrapper}
-         >
+      >
          { msgArr ?
 
-            msgArr.map(msg => <CMsg               
-                                 key={msg._id} 
+            msgArr.map(msg => 
+                              <CMsg               
+                                 key={msg.nextId} 
                                  msg={msg} 
-                                 prevOwner={prevOwner}
-                                 prevTime={prevTime}
-                               />) :
+                                 onEdit={onEdit}
+                               /> ) :
                
                      <div>сообщений нема</div>         
          }  

+ 51 - 17
src/components/SendingField.jsx

@@ -8,9 +8,10 @@ import CloseIcon from '@mui/icons-material/Close';
 
 import {useDropzone} from 'react-dropzone';
 import {render} from 'react-dom';
-import {sortableContainer, sortableElement} from 'react-sortable-hoc';
+import {SortableContainer, SortableElement} from 'react-sortable-hoc';
 import {arrayMoveImmutable} from 'array-move';
 
+import { backURL }  from '../helpers'
 import {connect}  from 'react-redux'
 import {
    actionSendMsg
@@ -86,7 +87,7 @@ const thumbsContainer = {
 
 
 
- const SortableItem = sortableElement(({onDelete, file}) => (
+ const SortableItem = SortableElement(({onDelete, file}) => (
    <div style={thumb}>         
       <div style={closeBtn}>
          <CloseIcon 
@@ -99,7 +100,7 @@ const thumbsContainer = {
       { 
          (file.type.split('/')[0] === 'image') ? 
             <img
-               src={file.preview}
+               src={file.url}
                style={img}
             />  :
             
@@ -110,7 +111,7 @@ const thumbsContainer = {
                   loop
                   // не работает
                   // playbackrate={'10'}
-                  src={file.preview}
+                  src={file.url}
                   style={img}
                >
                </video> :
@@ -127,7 +128,7 @@ const thumbsContainer = {
    </div>
  ));
 
- const SortableContainer = sortableContainer(({children}) => {
+ const SortableList = SortableContainer(({children}) => {
 
    return (
       <div style={thumbsContainer}>
@@ -139,7 +140,7 @@ const thumbsContainer = {
 
 
 
-const MsgDropZone = ({ setText, setFiles, files, text, onEnter }) => {
+const MsgDropZone = ({ setText, setFiles, setMsgId, files, text, onEnter }) => {
 
    const {
       acceptedFiles,
@@ -151,7 +152,7 @@ const MsgDropZone = ({ setText, setFiles, files, text, onEnter }) => {
             // console.log(acceptedFiles)
             await setFiles([...files, 
                ...acceptedFiles.map(file => Object.assign(file, {
-                  preview: URL.createObjectURL(file)
+                  url: URL.createObjectURL(file)
                }))
             ])
 
@@ -170,8 +171,13 @@ const MsgDropZone = ({ setText, setFiles, files, text, onEnter }) => {
    
    // useEffect(() => {
    // // Make sure to revoke the data uris to avoid memory leaks
-   // files.forEach(file => URL.revokeObjectURL(file.preview));
+   // files.forEach(file => URL.revokeObjectURL(file.url));
    // }, [files]);
+   const textArea = useRef(null)
+
+   useEffect(() => {
+      textArea.current.focus()
+   })
 
   return (
    <>
@@ -179,21 +185,21 @@ const MsgDropZone = ({ setText, setFiles, files, text, onEnter }) => {
          <section style={containerWrapp}>
 
 
-            <SortableContainer 
+            <SortableList 
                onSortEnd={onSortEnd}
                axis={'xy'}
                pressDelay={100}
             >
                {files.map((file, i) => (
                   <SortableItem 
-                     key={file.preview} 
+                     key={file.url} 
                      index={i} 
                      file={file}
                      onDelete={() => onDelete(i)}
                      axis={'xy'}
                   />
                ))}
-            </SortableContainer>
+            </SortableList>
         
 
             <div {...getRootProps({className: 'dropzone'})} style={{ display: 'flex', alignItems: 'center'}}>
@@ -201,7 +207,8 @@ const MsgDropZone = ({ setText, setFiles, files, text, onEnter }) => {
 
                   <AttachFileIcon fontSize="large" sx={{ cursor: 'pointer' }} />
                   
-                  <TextareaAutosize  
+                  <TextareaAutosize
+                     ref={textArea}  
                      value={text}                   
                      minRows={4}
                      maxRows={10}
@@ -216,6 +223,7 @@ const MsgDropZone = ({ setText, setFiles, files, text, onEnter }) => {
                                  onEnter()
                            setText('')
                            setFiles([])
+                           setMsgId()
                         }
                      }}
                   />
@@ -228,22 +236,47 @@ const MsgDropZone = ({ setText, setFiles, files, text, onEnter }) => {
 }
 
 
-const SendingField = ({ chatId, onSend }) => {
-   const [text, setText] = useState('')
-   const [files, setFiles] = useState([])
+const SendingField = ({ chatId, onSend, msg }) => {
+
+   const [text, setText] = useState(msg?.text || '')
+   const [files, setFiles] = useState(msg?.media.map(mediaFile =>( {...mediaFile, url: backURL +  mediaFile.url} )) || [])
+   const [msgId, setMsgId] = useState(msg?._id)
+
+   useEffect(() => {
+      setText(msg?.text || '')
+      setFiles(msg?.media.map(mediaFile =>( {...mediaFile, url: backURL +  mediaFile.url} )) || [])
+      setMsgId(msg?._id)
+   },[msg])
 
    return (
       <Box sx={{ display: 'flex', alignItems: 'stratch', flexDirection: 'column',
             height: '100%', width: '100%'}} >
       
          <Box sx={{ flexGrow: 1, flexShrink: 1, overflow: 'auto', backgroundColor: '#fff' }}>
+
+            { msgId &&
+               <Box sx={{ flexGrow: 1, flexShrink: 1}}>
+                  <Button 
+                     sx={{ width: "100%" }}
+                     variant="contained" 
+                     onClick={() => {
+                        setText('')
+                        setFiles([])
+                        setMsgId()
+                     }}
+                  >
+                     Отменить редактирование
+                  </Button>
+               </Box>
+            }
             
             <MsgDropZone 
                setText={setText} 
                setFiles={setFiles} 
+               setMsgId={setMsgId}
                files={files} 
                text={text} 
-               onEnter={() => onSend(chatId, text.trim(), "media", files)} />
+               onEnter={() => onSend(chatId, text.trim(), "media", files, msgId)} />
          
          </Box>         
          <Box sx={{ flexGrow: 1, flexShrink: 1}}>
@@ -253,9 +286,10 @@ const SendingField = ({ chatId, onSend }) => {
                endIcon={<SendIcon />} 
                onClick={() => {
                   (text.match(/^\s*$/) && files.length === 0) ||
-                        onSend(chatId, text.trim(), "media", files) 
+                        onSend(chatId, text.trim(), "media", files, msgId) 
                   setText('')
                   setFiles([])
+                  setMsgId()
                }}
             >
                Отправить

+ 4 - 4
src/components/index.js

@@ -1,9 +1,9 @@
 // универсальные компоненты, повторяющиеся на многих страницах
 import {CChatList} from './ChatList'
 import {CMsgList} from './MsgList'
-import {CMsg, MsgMenu} from './Msg'
+import {CMsg} from './Msg'
 import {CPreloaded} from './Preload'
-import {MainMenu, MenuDrawer} from './MainMenu'
+import { MenuDrawer} from './MainMenu'
 import {Header} from './Header'
 import {SearchBlock} from './SearchBlock'
 import {FloatBtn} from './FloatBtn'
@@ -19,9 +19,9 @@ import {CChatMngHeader} from './ChatMngHeader'
 
 export {CChatList}
 export {CMsgList} 
-export {CMsg, MsgMenu} 
+export {CMsg} 
 export {CPreloaded} 
-export {MainMenu, MenuDrawer} 
+export { MenuDrawer} 
 export {Header}  
 export {SearchBlock}  
 export {FloatBtn}  

+ 43 - 0
src/helpers/decorateLinks.js

@@ -0,0 +1,43 @@
+
+
+export const decorateLinks = (text, style) => {
+
+   const linkRegEx = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/
+   const youtubeRegEx = /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?‌​[\w\?‌​=]*)?/
+
+   let matchLink = text.match(linkRegEx)
+   let matchYoutube = text.match(youtubeRegEx)
+
+   if (matchLink) {
+       let [link] = matchLink
+       if (link) {
+           let newLink = <a href={link} style="color:blue">{link}</a>
+           let newText = null
+           newText = text.replace(link, newLink)
+           if (matchYoutube) {
+               let [, youtubeKey] = matchYoutube
+   
+               if (youtubeKey) {
+                   return (
+                     <>
+                        <pre>{newText}</pre> <br/> <iframe width="560" height="315" 
+                        src="https://www.youtube.com/embed/${youtubeKey}" title="YouTube video player" 
+                        frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; 
+                        picture-in-picture" allowfullscreen></iframe>
+                     </>
+                   )
+
+               } 
+           } else {
+               return (
+                  <pre>{newText}</pre>
+               ) 
+           }
+       }
+
+   } else {
+       return (
+         <pre>{text}</pre>
+       ) 
+   }
+}

+ 4 - 2
src/helpers/index.js

@@ -4,10 +4,12 @@ import {printStrReq, printEnding} from './printStrReq'
 import {passReq} from './passReq'
 import {dateFromStamp} from './dateFuncs'
 import {stringColor} from './stringColorFuncs'
-
+import {decorateLinks} from './decorateLinks'
 
 export {backURL, gql} 
 export {printStrReq, printEnding} 
 export {passReq} 
 export {dateFromStamp} 
-export {stringColor} 
+export {stringColor} 
+export {decorateLinks} 
+

+ 47 - 11
src/pages/MsgPage.jsx

@@ -8,9 +8,13 @@ import {
 } from "../components"
 
 import {
-   actionFullMsgsByChat
+   actionFullMsgsByChat,
  } from "../actions"
+ import {
+   actionChatOne
+ } from "../reducers"
  import { connect } from 'react-redux'
+import { Repeat } from '@mui/icons-material';
 
 
 
@@ -41,17 +45,40 @@ const msgSend = {
    maxHeight: "60%",
 }
 
-const MsgBlock = ({ chatId, getMsgs, msgsCount=20 }) => {
 
-   const [msgsBlock, setMsgsBlock] = useState(1)
+ 
+
+const MsgBlock = ({ chatId, scroll, setScroll, getMsgs, setLastVizited, msgsCount=20 }) => {
+
+   const [msgToEdit, setmMsgToEdit] = useState()
+
+   const [msgsBlock, setMsgsBlock] = useState(0)
 
    // перезагрузка и обновление стейта происходит тут ??????????
    // надо как то отлавливать первый не первый рендер ????????
 
+   const divRef = useRef(null)
+   // useEffect(() => {
+   //    divRef.current.scrollTop = scroll[chatId]
+   //    console.log(scroll)
+   // },[scroll])
+
+
    useEffect(() => {
       getMsgs(chatId, msgsBlock, msgsCount)
    },[msgsBlock])
 
+   // useEffect(() => {
+   //    return () => {
+   //       setLastVizited({
+   //             _id: chatId, 
+   //             // lastVizited: (Math.ceil((new Date()).getTime()/1000).toString(16) + '0'.repeat(16) )
+   //             lastVizited: (new Date()).getTime()
+   //          }
+   //       )  
+   //    }
+   // },)
+
    return (
       <>  
          <div style={msgPageContainer}>
@@ -68,17 +95,20 @@ const MsgBlock = ({ chatId, getMsgs, msgsCount=20 }) => {
 
                <div 
                   style={msgs}
+                  ref={divRef}
                   onScroll={(e) => {
+                     // setScroll({...scroll, [chatId]: e.target.scrollTop})
+
                      if (e.target.scrollTop*(-1) >= e.target.scrollHeight - e.target.clientHeight) {
                         setMsgsBlock(msgsBlock => msgsBlock + msgsCount)
                      }
                   }}
                   >
-                  <CMsgList chatId={chatId} />
+                  <CMsgList chatId={chatId} onEdit={setmMsgToEdit} />
                </div>
 
                <div style={msgSend}>
-                  <CSendingField chatId={chatId} />
+                  <CSendingField chatId={chatId} msg={msgToEdit} />
                </div>       
 
             </div>
@@ -87,10 +117,16 @@ const MsgBlock = ({ chatId, getMsgs, msgsCount=20 }) => {
       </>
    )
 }
-const CMsgBlock = connect( null, {getMsgs: actionFullMsgsByChat})(MsgBlock)
+const CMsgBlock = connect( null, {getMsgs: actionFullMsgsByChat, setLastVizited: actionChatOne })(MsgBlock)
 
-export const MsgPage = ({ match:{params:{_id}} }) => (
-   <>
-      <CMsgBlock chatId={_id} key={_id} />
-   </>
-)
+
+export const MsgPage = ({ match:{params:{_id}} }) => {
+
+   const [scroll, setScroll] = useState({})
+
+   return (
+      <>
+         <CMsgBlock chatId={_id} scroll={scroll} setScroll={setScroll} key={_id} />
+      </>
+   )
+}

+ 20 - 7
src/reducers/chatsReducer.js

@@ -35,10 +35,10 @@
 
       const newMsgState = msgState.sort((a, b) => {
         if (a._id > b._id) {
-          return -1
+          return 1
         }
         if (a._id < b._id) {
-          return 1
+          return -1
         }
         return 0
       })
@@ -46,9 +46,21 @@
       return newMsgState
     }
 
-    // перебрать, проверить, что везде есть ключи, если где то нет, использовать те что есть
-    // function refreshMembers(newMembers, oldMembers) {
-    // }
+
+    function getInfoAboutNext(msgState) {
+      const informedState = []
+
+      for (let i = 0; i < msgState.length; i++) {
+        const msg = msgState[i]
+        
+        msg.nextId = msgState[i + 1]?._id || null
+        msg.nextOwner = msgState[i + 1]?.owner?._id || null
+        msg.nextTime = msgState[i + 1]?.createdAt || 0
+        informedState.push(msg)
+      }
+
+      return informedState
+    }
 
 
     function sortChats(unsortedChats) {
@@ -91,7 +103,7 @@
                 // если массив, то выясняем какой и вызываем соответствующие функции слияния
                 if (Array.isArray(newValue)) {
                   if (key === 'messages') {
-                    oldChats[chat._id][key] = refreshMsgs(newValue, oldValue)
+                    oldChats[chat._id][key] = getInfoAboutNext(refreshMsgs(newValue, oldValue))
                   }
                   if (key === 'members') {
                     oldChats[chat._id][key] = newValue
@@ -132,7 +144,8 @@
 
           const msgState = state[chatId]?.messages || []
           
-          const newMsgState = refreshMsgs(payload, msgState)
+          const newMsgState = getInfoAboutNext(refreshMsgs(payload, msgState))
+          // const newMsgState = refreshMsgs(payload, msgState)
 
           const newState = {
             ...state,

+ 6 - 4
src/reducers/store.js

@@ -82,12 +82,12 @@ socket.on('chat', async (chat) => {
 
    const state = store.getState()
    const myId = state.auth.payload?.sub?.id
+   // приходится делать так, так как овнер не приходит по сокету
    const ownerId = state.chats[chat._id]?.owner?._id
 
    if (myId !== ownerId) {
       playAudio(chatSound)
    }
-
    store.dispatch(actionChatOne(chat)) 
 
    let chatFull = await store.dispatch(actionGetChatById(chat._id))
@@ -104,11 +104,13 @@ socket.on('chat_left', async (chat) => {
    if (myId !== ownerId) {
       playAudio(chatSound)
       store.dispatch(actionChatLeft(chat)) 
+   } else {
+      store.dispatch(actionChatOne(chat)) 
+
+      let chatFull = await store.dispatch(actionGetChatById(chat._id))
+      await store.dispatch(actionChatOne(chatFull))
    }
-   store.dispatch(actionChatOne(chat)) 
 
-   let chatFull = await store.dispatch(actionGetChatById(chat._id))
-   await store.dispatch(actionChatOne(chatFull))
 })