├── .drone.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── demo ├── .eslintrc.js ├── .storybook │ ├── addons.js │ ├── config.js │ └── webpack.config.js ├── README.md ├── build.zip ├── config │ ├── env.js │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── paths.js │ ├── polyfills.js │ ├── webpack.config.dev.js │ ├── webpack.config.prod.js │ └── webpackDevServer.config.js ├── fontdemo │ ├── font │ │ ├── demo.css │ │ ├── demo.html │ │ ├── demo_fontclass.html │ │ ├── demo_symbol.html │ │ ├── demo_unicode.html │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ ├── fontello │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ ├── config.json │ │ ├── css │ │ │ ├── animation.css │ │ │ ├── fontello-codes.css │ │ │ ├── fontello-embedded.css │ │ │ ├── fontello-ie7-codes.css │ │ │ ├── fontello-ie7.css │ │ │ └── fontello.css │ │ ├── demo.html │ │ └── font │ │ │ ├── fontello.eot │ │ │ ├── fontello.svg │ │ │ ├── fontello.ttf │ │ │ ├── fontello.woff │ │ │ └── fontello.woff2 │ └── iconfont │ │ ├── demo.css │ │ ├── demo_fontclass.html │ │ ├── demo_symbol.html │ │ ├── demo_unicode.html │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── scripts │ ├── build.js │ ├── start.js │ └── test.js ├── src │ ├── App.css │ ├── App.js │ ├── Routes.js │ ├── components │ │ ├── blacklist │ │ │ ├── BlacklistModal.js │ │ │ └── style │ │ │ │ └── BlacklistModal.less │ │ ├── chat │ │ │ ├── Audio.js │ │ │ ├── ChatEmoji.js │ │ │ ├── ChatMessage.js │ │ │ ├── ReplyMessage.js │ │ │ └── style │ │ │ │ ├── ChatEmoji.less │ │ │ │ ├── ChatMessage.less │ │ │ │ ├── ReplyMessage.less │ │ │ │ └── index.less │ │ ├── common │ │ │ ├── AppliedRoute.js │ │ │ ├── AsyncComponent.js │ │ │ ├── AsyncComponentBak.js │ │ │ ├── EditInput.js │ │ │ ├── FadingRoute.js │ │ │ ├── LoadingComponent.js │ │ │ ├── ModalComponent.js │ │ │ └── style │ │ │ │ ├── ModalComponent.less │ │ │ │ └── webrtc.less │ │ ├── contact │ │ │ ├── ContactHead.js │ │ │ ├── ContactItem.js │ │ │ ├── UserInfoModal.js │ │ │ └── style │ │ │ │ └── index.less │ │ ├── friend │ │ │ ├── AddFriendsModal.js │ │ │ └── FriendsRequestModal.js │ │ ├── group │ │ │ ├── AddGroupModal.js │ │ │ ├── GroupInfo.js │ │ │ ├── GroupInviteModal.js │ │ │ ├── GroupMembers.js │ │ │ ├── GroupRequestModal.js │ │ │ ├── JoinGroupModal.js │ │ │ └── style │ │ │ │ ├── AddGrouModal.less │ │ │ │ ├── JoinGroupModal.less │ │ │ │ └── index.less │ │ ├── header │ │ │ ├── HeaderOps.js │ │ │ ├── HeaderTab.js │ │ │ └── style │ │ │ │ ├── HeaderOps.less │ │ │ │ ├── HeaderTab.less │ │ │ │ └── index.less │ │ ├── list │ │ │ ├── ListItem.js │ │ │ └── style │ │ │ │ └── index.less │ │ ├── recorder │ │ │ ├── index.jsx │ │ │ ├── index.less │ │ │ └── recordAudio.js │ │ ├── videoCall │ │ │ ├── AddAVMemberModal.js │ │ │ ├── AlertModal.js │ │ │ ├── Channel.js │ │ │ ├── MiniModal.js │ │ │ ├── MultiAVModal.js │ │ │ └── PtopCallModal.js │ │ └── videoSetting │ │ │ └── videoSettingModal.js │ ├── config │ │ ├── EMedia_sdk-dev.js │ │ ├── EMedia_x1v1_3.4.1.js │ │ ├── Easemob-chat-3.6.3.js │ │ ├── Easemob-chat-4.0.9.js │ │ ├── Easemob-chat-4.1.0.js │ │ ├── Easemob-chat-4.1.1.js │ │ ├── Easemob-chat-4.1.2.js │ │ ├── Easemob-chat-4.1.3.js │ │ ├── Easemob-chat-4.1.4.js │ │ ├── Easemob-chat-4.1.5.js │ │ ├── Easemob-chat-4.1.7.js │ │ ├── Easemob-chat-4.2.0.js │ │ ├── Easemob-chat-4.2.1.js │ │ ├── Easemob-chat-4.3.0.js │ │ ├── Easemob-chat-4.4.0.js │ │ ├── Easemob-chat-4.5.0.js │ │ ├── Easemob-chat-4.5.1.js │ │ ├── Easemob-chat-4.6.0.js │ │ ├── Easemob-chat-4.7.0.js │ │ ├── Easemob-chat-4.8.0.js │ │ ├── Easemob-chat-4.8.1.js │ │ ├── Easemob-chat.d.ts │ │ ├── WebIM.js │ │ ├── WebIMConfig.js │ │ ├── config.js │ │ ├── emoji.js │ │ ├── i18n │ │ │ ├── cn.js │ │ │ ├── index.js │ │ │ └── us.js │ │ └── index.js │ ├── const │ │ └── index.js │ ├── containers │ │ ├── chat │ │ │ ├── Chat.js │ │ │ └── style │ │ │ │ └── index.less │ │ ├── chinamobile │ │ │ ├── Chinamobile.js │ │ │ └── style │ │ │ │ └── index.less │ │ ├── contact │ │ │ └── Contact.js │ │ └── loginregister │ │ │ ├── Login.js │ │ │ ├── Register.js │ │ │ ├── ResetPassword.js │ │ │ ├── Server.js │ │ │ └── index.less │ ├── index.js │ ├── layout │ │ ├── DefaultLayout.js │ │ ├── Layout.js │ │ ├── RightSider.js │ │ ├── Sider.js │ │ └── style │ │ │ └── index.less │ ├── logo.svg │ ├── redux │ │ ├── BlacklistRedux.js │ │ ├── ChatRoomRedux.js │ │ ├── CommonRedux.js │ │ ├── ContactInfoScreenRedux.js │ │ ├── ContactsScreenRedux.js │ │ ├── CreateStore.js │ │ ├── DemoRedux.js │ │ ├── GroupInviteRedux.js │ │ ├── GroupMemberRedux.js │ │ ├── GroupRedux.js │ │ ├── GroupRequestRedux.js │ │ ├── IndexRedux.js │ │ ├── LoginRedux.js │ │ ├── MessageRedux.js │ │ ├── RegisterRedux.js │ │ ├── ResetPasswordRedux.js │ │ ├── RosterRedux.js │ │ ├── ServerRedux.js │ │ ├── StartupRedux.js │ │ ├── StrangerRedux.js │ │ ├── SubscribeRedux.js │ │ ├── VideoCallRedux.js │ │ ├── WebIMRedux.js │ │ └── index.js │ ├── registerServiceWorker.js │ ├── selectors │ │ ├── ChatSelector.js │ │ └── ContactSelector.js │ ├── themes │ │ ├── css │ │ │ └── fontello.css │ │ ├── faces │ │ │ ├── ee_1.png │ │ │ ├── ee_10.png │ │ │ ├── ee_11.png │ │ │ ├── ee_12.png │ │ │ ├── ee_13.png │ │ │ ├── ee_14.png │ │ │ ├── ee_15.png │ │ │ ├── ee_16.png │ │ │ ├── ee_17.png │ │ │ ├── ee_18.png │ │ │ ├── ee_19.png │ │ │ ├── ee_2.png │ │ │ ├── ee_20.png │ │ │ ├── ee_21.png │ │ │ ├── ee_22.png │ │ │ ├── ee_23.png │ │ │ ├── ee_24.png │ │ │ ├── ee_25.png │ │ │ ├── ee_26.png │ │ │ ├── ee_27.png │ │ │ ├── ee_28.png │ │ │ ├── ee_29.png │ │ │ ├── ee_3.png │ │ │ ├── ee_30.png │ │ │ ├── ee_31.png │ │ │ ├── ee_32.png │ │ │ ├── ee_33.png │ │ │ ├── ee_34.png │ │ │ ├── ee_35.png │ │ │ ├── ee_4.png │ │ │ ├── ee_5.png │ │ │ ├── ee_6.png │ │ │ ├── ee_7.png │ │ │ ├── ee_8.png │ │ │ └── ee_9.png │ │ ├── font │ │ │ ├── iconfont.css │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.js │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ ├── fontello │ │ │ ├── fontello.eot │ │ │ ├── fontello.svg │ │ │ ├── fontello.ttf │ │ │ ├── fontello.woff │ │ │ └── fontello.woff2 │ │ ├── iconfont │ │ │ ├── iconfont.css │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ ├── img │ │ │ ├── 1.jpg │ │ │ ├── acceptCall@2x.png │ │ │ ├── avatar-big@2x.png │ │ │ ├── avatar@2x.png │ │ │ ├── avtool.svg │ │ │ ├── camera-close@2x.png │ │ │ ├── camera@2x.png │ │ │ ├── default-img.png │ │ │ ├── hangupCall@2x.png │ │ │ ├── invite_member@2x.png │ │ │ ├── microphone-mute@2x.png │ │ │ ├── microphone@2x.png │ │ │ ├── minimodal@2x.png │ │ │ ├── narrow@2x.png │ │ │ ├── rtc-bg@2x.png │ │ │ ├── talking@2x.png │ │ │ ├── video-bg2.png │ │ │ ├── video-bg@2x.png │ │ │ └── video-loading@2x.png │ │ ├── ngprogress.less │ │ ├── theme.js │ │ ├── theme.less │ │ ├── webim │ │ │ ├── iconfont.css │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.js │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ └── webim2 │ │ │ ├── iconfont.css │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.js │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ └── utils │ │ ├── AppDB.js │ │ ├── history.js │ │ ├── index.js │ │ ├── loglevel.js │ │ └── overlay.js ├── stories │ └── index.js └── storybook-static │ └── favicon.ico ├── package-lock.json ├── simpleDemo ├── cert.pem ├── demo.html ├── easmeob-im │ ├── chatroom.js │ ├── friend.js │ ├── group.js │ ├── info.js │ ├── login.js │ └── message.js ├── key.pem ├── readme.md ├── sdk │ └── Easemob-chat-3.6.3.js └── utils │ ├── WebIMConfig.js │ ├── initWeb.js │ └── jquery-3.4.1.js └── travis.sh /.drone.yml: -------------------------------------------------------------------------------- 1 | workspace: 2 | base: /data/apps/opt 3 | path: webim-h5 4 | 5 | 6 | pipeline: 7 | 8 | restore-cache: 9 | image: drillster/drone-volume-cache 10 | restore: true 11 | mount: 12 | - node_modules 13 | - tag 14 | volumes: 15 | - /data/apps/opt/webim-h5:/cache 16 | 17 | build: 18 | image: node:7.8 19 | privileged: true 20 | commands: 21 | - cd sdk && npm link && cd .. 22 | - cd webrtc && npm link && cd .. 23 | - cd emedia && npm link && cd .. 24 | - npm link easemob-websdk 25 | - npm link easemob-webrtc 26 | - npm link easemob-emedia 27 | - cd demo && npm install 28 | - npm run build 29 | # 2.0 demo 相关编译操作在demo目录下面 30 | - cd .. 31 | - cp -rf demo/build image/docker/webim/webim 32 | - cp -rf demo/storybook-static image/docker/webim/storybook 33 | - echo 'build success' 34 | when: 35 | branch: [ dev, master ] 36 | 37 | 38 | dockerize-sandbox: 39 | image: plugins/docker 40 | environment: 41 | - DOCKER_LAUNCH_DEBUG=true 42 | debug: true 43 | repo: docker-registry-cn.easemob.com/kubernetes/im/webim 44 | #2.0会被drone截断成2 45 | tags: 20 46 | registry: docker-registry-cn.easemob.com 47 | secrets: [ docker_username, docker_password ] 48 | dockerfile: image/docker/webim/Dockerfile 49 | context: image/docker/webim/ 50 | when: 51 | branch: dev 52 | 53 | deploy-sandbox: 54 | image: docker-registry-cn.easemob.com/kubernetes/im/webim-h5-deploy:latest 55 | pull: true 56 | environment: 57 | - DOCKER_LAUNCH_DEBUG=true 58 | - TAG=20 59 | secrets: [ ssh_key, jumpserver_host, jumpserver_port, sandbox_host ] 60 | debug: true 61 | volumes: 62 | - /var/run/docker.sock:/var/run/docker.sock 63 | when: 64 | branch: dev 65 | 66 | dockerize-online: 67 | image: plugins/docker 68 | environment: 69 | - DOCKER_LAUNCH_DEBUG=true 70 | debug: true 71 | repo: docker-registry-cn.easemob.com/kubernetes/im/webim 72 | tags: ${DRONE_COMMIT:0:7} 73 | registry: docker-registry-cn.easemob.com 74 | secrets: [ docker_username, docker_password ] 75 | dockerfile: image/docker/webim/Dockerfile 76 | context: image/docker/webim/ 77 | when: 78 | branch: master 79 | 80 | deploy-online: 81 | image: docker-registry-cn.easemob.com/kubernetes/im/webim-h5-online:latest 82 | pull: true 83 | environment: 84 | - DOCKER_LAUNCH_DEBUG=true 85 | - TAG=${DRONE_COMMIT:0:7} 86 | secrets: [ ssh_key, jumpserver_host, jumpserver_port, online_host ] 87 | debug: true 88 | volumes: 89 | - /data/tag/webim-h5:/data/tag/webim-h5 90 | - /var/run/docker.sock:/var/run/docker.sock 91 | when: 92 | branch: master 93 | 94 | rollback-online: 95 | image: docker-registry-cn.easemob.com/kubernetes/im/webim-h5-rollback:latest 96 | pull: true 97 | environment: 98 | - DOCKER_LAUNCH_DEBUG=true 99 | secrets: [ ssh_key, jumpserver_host, jumpserver_port, online_host ] 100 | debug: true 101 | volumes: 102 | - /data/tag/webim-h5:/data/tag/webim-h5 103 | - /var/run/docker.sock:/var/run/docker.sock 104 | when: 105 | branch: rollback 106 | 107 | rebuild-cache: 108 | image: drillster/drone-volume-cache 109 | rebuild: true 110 | mount: 111 | - node_modules 112 | - tag 113 | volumes: 114 | - /data/apps/opt/webim-h5:/cache 115 | 116 | notify: 117 | image: drillster/drone-email 118 | port: 25 119 | secrets: [ plugin_host, plugin_from, plugin_username, plugin_password ] 120 | when: 121 | status: [ failure, success ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | /.project 4 | .idea/* 5 | *.swp 6 | demo/build/* 7 | *.map 8 | *.vscode 9 | publish/* 10 | npm-debug.log 11 | web-im 12 | web-im-*.zip 13 | image/docker/webim/webim 14 | coverage/* 15 | # demo/javascript/dist/demo-*.js 16 | # sdk/dist/websdk-*.js 17 | # sdk/dist/websdk-*.js.map 18 | # webrtc/dist/webrtc-*.js 19 | demo/remote_docker_restart.sh 20 | *.orig 21 | demo/*.sh 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12.4" 4 | install: true 5 | script: 6 | - chmod 755 ./travis.sh 7 | - "./travis.sh" 8 | env: 9 | global: 10 | - secure: "dLoqONm8TDA1rNluFU+kAeu/xKpPevvA/N4/mL88pd8vNV2A8UnzOofjrSfruvsHFtmk28H3n81hLZddL0rH7njnd6Cp6hyGsKMGyO8W4zcrb1VWie5Dv5t+WuC/c0at8zu2ik/7z/FAC/Xr5BRmuujtphG/rz14O3wviikXRSXqhrmThqjFDjHK3EwbRtC9ZjswudfwtF41GASi6biZS7jrufu3b/YCgEMYad9/EldcbcMQnIDKZoCUum5u2qI3Mov6d0SFrhnhcsBRbijiVkh4zkdgKW/Gc25O66scw+OyXFpiJE13vKHy8Q2+ut+06V2eU5dpOcC//ijQeBBk1tUqT0trzgRcIrQfK7j9nCYCHssg2egtMJuiIXPwMNR9OmAGQTV5se+h2OSfRs5VyCgrAGi5LuDUCBjnz+I8r/AFlQGbZrBt0f8DQzDmjmHvGorKavIU+xvxkS0z4CnXyZd92KE8HCFkFB7dCytPZW7JIhBkEQIy6gBCXL4xMzhNPWc7bFbFNohHuEP1+StIvJIJDtlCUyoLG7X3GAaKDHtwx9EMiGghbdVIM9jbczFg6e/M9EOhtgb4olsr+NDTt5FpJPSP8zHWh5UI60MAuUWiwJ4Hp94AnmC+NzZ0MOcFhkcwvAwYdlSol24J4sYl3ntQL+scQRs5IoBAFHTg3UQ=" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 版本更新说明: 2 | ## v3.3.2 @2020-10-19 3 | * [IM SDK] 增加支持设置固定 deviceId 4 | * [IM SDK] 修改 getGroup 方法去掉参数 5 | * [IM SDK] 修复拉历史消息 bug 6 | * [IM SDK] 修复发送附件消息对3.3.0之前 api 的兼容问题 7 | * [IM SDK] 修复使用 uniapp 打包的 app,退到后台回来时websocket无法连接的问题 8 | * [webim demo] 增加提示注册失败原因:用户名超出64字节 9 | * [webim demo] 增加群组非管理员邀请其他用户进群没有权限提示 10 | * [webim demo] 修复部分已知bug 11 | 12 | 13 | ## v3.2.5 @2020-6-10 14 | ### Feature 15 | * [demo] 1v1视频无法互通 16 | * [demo] 使用v3.1.2 IM SDK 17 | 18 | ## v3.2.0 @2020-5-15 19 | ### Feature 20 | * [demo] 群组管理员对群成员进行禁言、拉黑等操作没有权限时增加提示 21 | * [demo] web端A给webB发起音视频会议,B接起后邀请手机端C,C没有收到邀请信息 22 | * [demo] web端发送给小程序和iOS IM SDK Demo的语音消息,时长都显示为0” 23 | * [demo] web端发送的语音,iOS端无法播放 24 | * [demo] 使用v3.1.2 SDK 25 | 26 | ## v3.1.6 @2020 27 | ### Feature 28 | * [demo] iOS客户端邀请chrome浏览器加入音视频会议,chrome浏览器收到会议邀请时未有弹窗,导致无法接受邀请 2020/3/4 29 | * [demo] 切换聊天室时还能收到上一个聊天室的通知消息 2020/3/18 30 | 31 | 32 | ## v3.1.5 @2020-2-19 33 | ### Feature 34 | * [demo] 音视频会议支持小程序 35 | * [demo] 增加共享桌面 36 | * [demo] 部分bug 37 | 38 | ## v3.0.17 @2019-12-31 39 | ### Feature 40 | * [demo] 去掉依赖多人音视频sdk emedia 只因用webrtc 41 | * [demo] 以及imsdk 去掉默认恢复deliver 只有单聊并且配置里设置为true才发送 42 | * [demo] 加发送语音 43 | * [demo] 用户注册失败加提示 44 | * [demo] 修复不能重复发送相同图片文件 45 | * [demo] 以及 emedia 修复在http下demo打不开 46 | 47 | ## v3.0.5 @2019-08-22 48 | ### Feature 49 | * [demo] 修改移除好友的回调 50 | * [demo] 修改简单demo添加黑名单/移除黑名单 51 | * [sdk] 扩展消息增加发送json对象 52 | * [sdk] 简化添加黑名单/移除黑名单API 53 | * [sdk] 修复electron下socket建立不成功 54 | 55 | ## v3.0.4 @2019-07-30 56 | ### Feature 57 | * [demo] 增加消息撤回 58 | * [demo] 简单demo增加发送视频文件实现 59 | * [sdk] 修复无法发送扩展消息bug 60 | 61 | ## v3.0.2 @2019-07-18 62 | ### Feature 63 | * [demo] 优化部分简单demo 64 | * [sdk] 下上传文件走dns 65 | * [sdk] 修复无法拉取历史消息bug 66 | * [sdk] 修复loc/cmd消息 messageId bug 67 | 68 | ## v3.0.1 @2019-07-09 69 | ### Feature 70 | 71 | * [demo] 修复简单demo未设置appkey问题 72 | * [demo] 基于3.0sdk添加好友去掉反添加好友过程 73 | * [demo] 根据需求调整项目结构 74 | * [demo] 解决起服务是打印警告问题 75 | 76 | 77 | ## v3.0.0 @2019-06-29 78 | ### Feature 79 | 80 | * [demo] 增加接受群邀请功能 81 | * [demo] 增加和调整一些群操作通知 82 | * [demo] 使用最新基于私有协议的sdk 83 | * [demo] 拆分sdk源码为单独repo 84 | * [demo] 修复部分bug 85 | * [sdk] 基于私有协议重写 86 | * [sdk] 增加拉取历史消息接口 87 | * [sdk] 增加撤回消息接口 88 | * [sdk] 增加接受群邀请接口 89 | 90 | ## v1.11.1 @2019-03-18 91 | ###Feature 92 | 93 | * [sdk] 通过设置isHttpDNS为true,从服务端获取DNS配置文件 94 | * [demo] 配置文件文件增加配置isHttpDNS 95 | * [demo] 项目初始化sdk增加isHttpDNS 96 | * [demo] 解决safari视频无图片、无声音问题 97 | 98 | 99 | ###BugFix 100 | 101 | 102 | ## v1.10.0 @2018-09-17 103 | 104 | ###Feature 105 | 106 | * [demo] 多人音视频 107 | 108 | ###BugFix 109 | 110 | * [demo] 在视频界面中,切到其他界面,视频界面不在了。但是视频还在继续 中 111 | * [demo] 火狐 邀请 chrome, 进入多人会议,都收不到视频通知 112 | * [demo] 不选择会话,收不到视频来电 113 | * [demo] 多人视频 开关视频键状态不对 114 | * [demo] chrome和firfox多人音视频会议中,chrome不显示firefox用户的视频 115 | * [demo] 多人视频,一个浏览器登录两个账号,有一个账号 ui经常收不到视频邀请 116 | 117 | 118 | ## v1.6.0 @2018-01-29 119 | 120 | ###Feature 121 | 122 | * [demo] 多人音视频 123 | * [sdk] 多人音视频 124 | 125 | ###BugFix 126 | 127 | * [demo] 无法发送表情 128 | 129 | ## v1.5.0 @2017-11-17 130 | 131 | ###Feature 132 | 133 | * [demo] 添加Rest Interface的Test case 134 | * [demo] sdk/demo 上传功能兼容ie8 135 | 136 | ###BugFix 137 | 138 | * [demo] 多设备登录异常 139 | * [demo] 新建需要审批的公有群,加入必须有审批流程 140 | * [demo] 鼠标悬浮在群禁言图标上出现提示信息“禁言” 141 | * [demo] demo.html中从cdn引入sdk 142 | * [demo] 修复无法准确统计离线消息数的bug 143 | * [demo] window.history.pushState在windows的chrome上有兼容性问题,统一改成window.location.href 144 | * [demo] window.location.href = xxxx,如果修改的是href.search参数(?a=x&b=y)时候, 如果遇到file方式打开本地index.html会直接跳转页面,造成登录一直不成功,改成修改 href.hash 参数(#a=x&b=y) 145 | * [demo] 将群管理员可操作的项目展示给管理员 146 | 147 | ## v1.4.11 @ 2017-09-22 148 | 149 | ###Feature 150 | 151 | * [demo] 响应式布局,一套Demo同时支持PC和H5,自适应不同终端屏幕尺寸 152 | * [demo] 完全基于React + Redux的单向数据流 153 | * [demo] 引入ant-design组件库,方便开发者后续开发 154 | * [demo] 专为移动端h5打造的webim 适配微信/QQ 和各种手机浏览器 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Demo(WebIM)介绍 2 | 3 | 4 | 更新时间:2023-04-10 5 | 6 | ----- 7 | ## 新增群组Mentions 功能, 使用Antd Mentions组件 8 | 9 | 实现方案: 10 | 11 | 文本消息新增扩展字段`em_at_list`, 表示被@的用户ID, 12 | 13 | - 'ALL' 表示@所有人 14 | - ['userId', 'userId2'] 表示@指定用户 15 | 16 | ```javascript 17 | const MENTION_ALL = 'ALL'; 18 | let mentionList = ['userId', 'userId1']; 19 | let isMentionALl = true 20 | 21 | let txt = { 22 | to: 'groupId', 23 | chatType: 'groupChat', 24 | msg: '@user 你好', 25 | type: 'txt', 26 | ext: { 27 | em_at_list: isMentionALl ? MENTION_ALL : atList 28 | } 29 | } 30 | ``` 31 | 收到文本消息, 如扩展字段包含`em_at_list`则表示为@消息, 32 | 如果`em_at_list`包含当前用户,则进行UI更新 33 | 34 | ```javascript 35 | // onTextMessage回调中 36 | let mentionList = message?.ext?.em_at_list 37 | // 如果存在mentionList, 并且不是当前用户多端同步的消息 38 | if(mentionList && message.from !== WebIM.conn.user){ 39 | // 如果是@所有人或者mentionList包含当前用户ID 40 | if(mentionList === MENTION_ALL || mentionList.includes(WebIM.conn.user)){ 41 | // 则进行UI更新 42 | } 43 | } 44 | ``` 45 | 46 | 47 | 更新时间:2022-07-15 48 | 49 | ----- 50 | 51 | 环信即时通讯 WEB 端提供示例应用可供体验。为方便体验,建议使用你自己的 Demo 应用,具体步骤如下: 52 | 53 | 1. 在 [环信即时通讯云 IM 管理后台](https://console.easemob.com/user/login) 通过邮箱注册,可以看到默认的 Demo 应用(默认应用是全功能开通的应用); 54 | 55 | 2. 在上图页面 Demo 应用右侧点击 **查看**,选择 **开放注册**; 56 | 57 | [![img](https://docs-im.easemob.com/_media/ccim/android/quickstart/3.png?w=600&tok=83515c)](https://docs-im.easemob.com/_detail/ccim/android/quickstart/3.png?id=ccim%3Aandroid%3Aquickstart%3Ademo) 58 | 59 | 3. 打开 Demo,点击 **服务器配置**; 60 | 61 | 62 | 4. 将 Demo 的 App Key 填入,点击 **保存配置**; 63 | 64 | 5. 然后点击 **注册用户** 进行体验。 65 | 66 | **注意** 67 | 68 | 注册模式分两种,开放注册和授权注册。只有开放注册时,才可以客户端注册。 69 | 70 | - 开放注册是为了测试使用,正式环境中不推荐使用该方式注册环信账号; 71 | - 授权注册的流程应该是你的应用服务器通过环信提供的 REST API 注册,之后将 token 保存到你的应用服务器或返回给客户端。 72 | 73 | ## 代码下载 74 | 75 | - 下载源代码:[github 源码地址](https://github.com/easemob/webim) 76 | 77 | 欢迎大家提交 PR 改进和修复 WebIM 中的问题。 78 | 79 | ## 运行 WebIM 工程 80 | 81 | 从[IM SDK 及 Demo 下载](https://www.easemob.com/download/im) 下载 WEB SDK 压缩包,然后解压。解压后在 `demo` 文件夹下,即为 WebIM 的工程目录。 82 | 83 | 1. 初始化安装 84 | - 在/demo下执行 `npm install` 85 | 86 | 2. 运行demo 87 | - `cd demo && npm start` 88 | 89 | http://localhost:3001 90 | - `cd demo && HTTPS=true npm start` (webrtc supports HTTPS only) 91 | 92 | https://localhost:3001 93 | 94 | ## 主要模块介绍 95 | 96 | 97 | Demo 中有几大模块 98 | 99 | - components —— 项目中定义的组件 100 | - config —— SDK 初始化配置 101 | - containers —— 容器组件,包含 contact, chat, login/regester 102 | - layout —— chat 部分的布局 103 | - selectors —— 缓存数据,优化性能 104 | - utils —— 数据库和工具方法 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "extends": "react-app", 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "experimentalObjectRestSpread": true, 11 | "jsx": true 12 | }, 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "react" 17 | ], 18 | "rules": { 19 | 20 | 'indent': [2, 4], // 强制使用一致的缩进 21 | 'eqeqeq': [2, 'always'], // 要求使用 === 和 !== 22 | 'semi': [2, 'never'], // 要求或禁止使用分号代替 ASI 23 | 'quotes': [2, 'single'], // 强制使用一致的反勾号、双引号或单引号 24 | 25 | "array-bracket-spacing": [ 26 | "error", 27 | "always" 28 | ], 29 | "object-curly-spacing": [ 30 | "error", 31 | "always" 32 | ], 33 | "semi": [ 34 | "error", 35 | "never" 36 | ], 37 | "template-curly-spacing": ["error", "never"], 38 | "jsx-a11y/label-has-associated-control": "off", 39 | "jsx-a11y/label-has-for": "off", 40 | "jsx-a11y/anchor-is-valid": [0], 41 | "jsx-a11y/no-static-element-interactions": [0], 42 | "jsx-a11y/click-events-have-key-events": [0], 43 | "jsx-a11y/alt-text": "off", 44 | "jsx-a11y/href-no-hash": "off" 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /demo/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ 2 | 3 | import '@storybook/addon-actions/register'; 4 | import '@storybook/addon-links/register'; 5 | -------------------------------------------------------------------------------- /demo/.storybook/config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ 2 | 3 | import { configure } from '@storybook/react'; 4 | 5 | function loadStories() { 6 | require('../stories'); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /demo/.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const autoprefixer = require("autoprefixer") 3 | 4 | function resolve(dir) { 5 | return path.join(__dirname, "..", dir) 6 | } 7 | 8 | console.log(resolve("src")) 9 | 10 | module.exports = { 11 | resolve: { 12 | alias: { 13 | "@": resolve("src") 14 | } 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.woff$|\.woff2$|\.eot$|\.ttf$|\.svg$|\.png$/, 20 | loader: require.resolve("file-loader"), 21 | options: { 22 | name: "static/media/[name].[hash:8].[ext]" 23 | } 24 | }, 25 | // { 26 | // test: /\.png$/, 27 | // loader: require.resolve("url-loader"), 28 | // options: {} 29 | // }, 30 | { 31 | test: /\.scss$/, 32 | loaders: ["style-loader", "css-loader", "sass-loader"], 33 | include: path.resolve(__dirname, "../") 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: [ 38 | require.resolve("style-loader"), 39 | { 40 | loader: require.resolve("css-loader"), 41 | options: { 42 | importLoaders: 1 43 | } 44 | }, 45 | { 46 | loader: require.resolve("postcss-loader"), 47 | options: { 48 | // Necessary for external CSS imports to work 49 | // https://github.com/facebookincubator/create-react-app/issues/2677 50 | ident: "postcss", 51 | plugins: () => [ 52 | require("postcss-flexbugs-fixes"), 53 | autoprefixer({ 54 | browsers: [ 55 | ">1%", 56 | "last 4 versions", 57 | "Firefox ESR", 58 | "not ie < 9" // React doesn't support IE8 anyway 59 | ], 60 | flexbox: "no-2009" 61 | }) 62 | ] 63 | } 64 | } 65 | ] 66 | }, 67 | // ** STOP ** Are you adding a new loader? 68 | // Remember to add the new extension(s) to the "file" loader exclusion list. 69 | { 70 | test: /\.less$/, 71 | use: [ 72 | require.resolve("style-loader"), 73 | { 74 | loader: require.resolve("css-loader") 75 | // options: { 76 | // importLoaders: 1 77 | // } 78 | }, 79 | { 80 | loader: require.resolve("postcss-loader"), 81 | options: { 82 | // Necessary for external CSS imports to work 83 | // https://github.com/facebookincubator/create-react-app/issues/2677 84 | ident: "postcss", 85 | plugins: () => [ 86 | require("postcss-flexbugs-fixes"), 87 | autoprefixer({ 88 | browsers: [ 89 | ">1%", 90 | "last 4 versions", 91 | "Firefox ESR", 92 | "not ie < 9" // React doesn't support IE8 anyway 93 | ], 94 | flexbox: "no-2009" 95 | }) 96 | ] 97 | } 98 | }, 99 | { 100 | loader: require.resolve("less-loader"), 101 | options: { 102 | // TODO 通过theme.js定义antd组件样式,并且支持按照依赖导入 https://ant.design/docs/react/customize-theme-cn 103 | // TODO custom style by theme.js, https://ant.design/docs/react/customize-theme-cn 104 | // modifyVars: getThemeConfig() 105 | } 106 | } 107 | ] 108 | } 109 | ] 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | ## 说明 2 | WebIM 3 (webim-h5) 在2.x的基础上, 主要做了以下更新: 3 | 1. 更换新版基于私有协议重写的sdk 4 | 2. 群组增加接受邀请/拒绝加群邀请功能 5 | 3. 修复部分bug 6 | WebIM 2 (webim-h5) 在1.x的基础上, 主要做了以下更新: 7 | 1. 响应式布局, 一套Demo同时支持PC和H5,自适应不同终端屏幕尺寸 8 | 2. 完全基于React + Redux的单向数据流 9 | 3. 引入ant-design组件库,方便开发者后续开发 10 | 4. 支持所有的现代浏览器(不支持IE6-11) 11 | 12 | ## 项目结构 13 | + config 为项目配置文件夹 14 | + fontdemo 为icon demo 15 | + scripts 为项目package.json中scripts所运行脚本 16 | + src 为项目源码 17 | 18 | ## 注意 19 | + sdk 文件夹下 webimSDK为即时通讯sdk, EMedia_x1v1为单人音视频sdk, EMedia_sdk-dev为多人音视频sdk,同时EMedia_x1v1依赖webimSDK, 音视频必须用https 20 | + simpleDemo 为简单demo,提供最简单直接的api调用示例 21 | + demo 为基于react+redux写的完整功能的demo 22 | + 更多关于sdk[集成文档](http://docs-im.easemob.com/im/web/intro/start) 23 | 24 | 25 | ## 安装 26 | 27 | 1. 初始化安装 28 | - 在/demo下执行 `npm i` `npm install` 29 | 30 | 2. 运行demo 31 | - `cd demo && npm start` (requires node@>=6) 32 | 33 | http://localhost:3001 34 | - `cd demo && HTTPS=true npm start` (webrtc supports HTTPS only) 35 | 36 | https://localhost:3001 37 | 38 | 3. 发布demo 39 | `cd demo && npm run build ` 40 | /demo/build 目录下的就是可以运行和部署的版本 41 | -------------------------------------------------------------------------------- /demo/build.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/build.zip -------------------------------------------------------------------------------- /demo/config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | var dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. 31 | // https://github.com/motdotla/dotenv 32 | dotenvFiles.forEach(dotenvFile => { 33 | if (fs.existsSync(dotenvFile)) { 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }); 37 | } 38 | }); 39 | 40 | // We support resolving modules according to `NODE_PATH`. 41 | // This lets you use absolute paths in imports inside large monorepos: 42 | // https://github.com/facebookincubator/create-react-app/issues/253. 43 | // It works similar to `NODE_PATH` in Node itself: 44 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 45 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 46 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 47 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 48 | // We also resolve them to make sure all tools using them work consistently. 49 | const appDirectory = fs.realpathSync(process.cwd()); 50 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 51 | .split(path.delimiter) 52 | .filter(folder => folder && !path.isAbsolute(folder)) 53 | .map(folder => path.resolve(appDirectory, folder)) 54 | .join(path.delimiter); 55 | 56 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 57 | // injected into the application via DefinePlugin in Webpack configuration. 58 | const REACT_APP = /^REACT_APP_/i; 59 | 60 | function getClientEnvironment(publicUrl) { 61 | const raw = Object.keys(process.env) 62 | .filter(key => REACT_APP.test(key)) 63 | .reduce( 64 | (env, key) => { 65 | env[key] = process.env[key]; 66 | return env; 67 | }, 68 | { 69 | // Useful for determining whether we’re running in production mode. 70 | // Most importantly, it switches React into the correct mode. 71 | NODE_ENV: process.env.NODE_ENV || 'development', 72 | // Useful for resolving the correct path to static assets in `public`. 73 | // For example, . 74 | // This should only be used as an escape hatch. Normally you would put 75 | // images into the `src` and `import` them in code to get their paths. 76 | PUBLIC_URL: publicUrl, 77 | } 78 | ); 79 | // Stringify all values so we can feed into Webpack DefinePlugin 80 | const stringified = { 81 | 'process.env': Object.keys(raw).reduce((env, key) => { 82 | env[key] = JSON.stringify(raw[key]); 83 | return env; 84 | }, {}), 85 | }; 86 | 87 | return { raw, stringified }; 88 | } 89 | 90 | module.exports = getClientEnvironment; 91 | -------------------------------------------------------------------------------- /demo/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /demo/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /demo/config/paths.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path") 4 | const fs = require("fs") 5 | const url = require("url") 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()) 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath) 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith("/") 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1) 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/` 20 | } else { 21 | return path 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 20 | 29 | WebIM-Demo 30 | 31 | 32 | 45 | 46 | <% var def = htmlWebpackPlugin.options; %> 47 | 48 | 49 | 52 |
53 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /demo/scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | const fs = require('fs'); 18 | const chalk = require('chalk'); 19 | const webpack = require('webpack'); 20 | const WebpackDevServer = require('webpack-dev-server'); 21 | const clearConsole = require('react-dev-utils/clearConsole'); 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 23 | const { 24 | choosePort, 25 | createCompiler, 26 | prepareProxy, 27 | prepareUrls, 28 | } = require('react-dev-utils/WebpackDevServerUtils'); 29 | const openBrowser = require('react-dev-utils/openBrowser'); 30 | const paths = require('../config/paths'); 31 | const config = require('../config/webpack.config.dev'); 32 | const createDevServerConfig = require('../config/webpackDevServer.config'); 33 | 34 | const useYarn = fs.existsSync(paths.yarnLockFile); 35 | const isInteractive = process.stdout.isTTY; 36 | 37 | // Warn and crash if required files are missing 38 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 39 | process.exit(1); 40 | } 41 | 42 | // Tools like Cloud9 rely on this. 43 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3001; 44 | const HOST = process.env.HOST || '0.0.0.0'; 45 | 46 | // We attempt to use the default port but if it is busy, we offer the user to 47 | // run on a different port. `detect()` Promise resolves to the next free port. 48 | choosePort(HOST, DEFAULT_PORT) 49 | .then(port => { 50 | if (port == null) { 51 | // We have not found a port. 52 | return; 53 | } 54 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 55 | const appName = require(paths.appPackageJson).name; 56 | const urls = prepareUrls(protocol, HOST, port); 57 | // Create a webpack compiler that is configured with custom messages. 58 | // const compiler = createCompiler(webpack, config, appName, urls, useYarn); 59 | const compiler = createCompiler({ webpack, config, appName, urls, useYarn }); 60 | // Load proxy config 61 | const proxySetting = require(paths.appPackageJson).proxy; 62 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic); 63 | // Serve webpack assets generated by the compiler over a web sever. 64 | const serverConfig = createDevServerConfig( 65 | proxyConfig, 66 | urls.lanUrlForConfig 67 | ); 68 | const devServer = new WebpackDevServer(compiler, serverConfig); 69 | // Launch WebpackDevServer. 70 | devServer.listen(port, HOST, err => { 71 | if (err) { 72 | return console.log(err); 73 | } 74 | if (isInteractive) { 75 | clearConsole(); 76 | } 77 | console.log(chalk.cyan('Starting the development server...\n')); 78 | openBrowser(urls.localUrlForBrowser); 79 | }); 80 | 81 | ['SIGINT', 'SIGTERM'].forEach(function (sig) { 82 | process.on(sig, function () { 83 | devServer.close(); 84 | process.exit(); 85 | }); 86 | }); 87 | }) 88 | .catch(err => { 89 | if (err && err.message) { 90 | console.log(err.message); 91 | } 92 | process.exit(1); 93 | }); 94 | -------------------------------------------------------------------------------- /demo/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | // process.on('unhandledRejection', err => { 12 | // throw err; 13 | // }); 14 | 15 | // Ensure environment variables are read. 16 | // require('../config/env'); 17 | // 18 | // const jest = require('jest'); 19 | // const argv = process.argv.slice(2); 20 | // 21 | // // Watch unless on CI or in coverage mode 22 | // if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | // argv.push('--watch'); 24 | // } 25 | // 26 | // 27 | // jest.run(argv); 28 | -------------------------------------------------------------------------------- /demo/src/Routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { 3 | // BrowserRouter as Router, 4 | HashRouter as Router, 5 | Route, 6 | Link, 7 | Redirect, 8 | Switch, 9 | withRouter 10 | } from 'react-router-dom' 11 | import AsyncComponents from '@/components/common/AsyncComponent' 12 | 13 | // import { asyncComponent } from "./utils" 14 | // import App from "./App" 15 | 16 | 17 | const LoadableMyComponent = AsyncComponents({ 18 | loader: () => import('./App') 19 | }) 20 | 21 | const LoadableMyComponent2 = AsyncComponents({ 22 | loader: () => import('./components/contact/ContactItem') 23 | }) 24 | 25 | // const AsyncApp = asyncComponent(() => import("./App")) 26 | 27 | const Routes = function ({ history, app }) { 28 | return ( 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | export default Routes 36 | -------------------------------------------------------------------------------- /demo/src/components/blacklist/BlacklistModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import { connect } from 'react-redux' 5 | import BlacklistActions from '@/redux/BlacklistRedux' 6 | import { I18n } from 'react-redux-i18n' 7 | import _ from 'lodash' 8 | import './style/BlacklistModal.less' 9 | 10 | class BlacklistModal extends React.Component { 11 | state = { 12 | name: '', 13 | screen: 1 14 | } 15 | 16 | render() { 17 | const { blacklist, doRemoveBlacklist } = this.props 18 | const items = blacklist.names.map((name, index) => { 19 | return ( 20 |

21 | {blacklist.byName[name]} 22 | { 28 | doRemoveBlacklist(name) 29 | }} 30 | /> 31 |

32 | ) 33 | }) 34 | 35 | return ( 36 |
37 |
38 |
39 | {items} 40 |
41 |
42 |
43 | ) 44 | } 45 | } 46 | 47 | export default connect( 48 | ({ entities }) => ({ 49 | blacklist: entities.blacklist 50 | }), 51 | dispatch => ({ 52 | doRemoveBlacklist: options => 53 | dispatch(BlacklistActions.doRemoveBlacklist(options)) 54 | }) 55 | )(BlacklistModal) 56 | -------------------------------------------------------------------------------- /demo/src/components/blacklist/style/BlacklistModal.less: -------------------------------------------------------------------------------- 1 | .x-blacklist { 2 | .ant-checkbox-group-item { 3 | display: block; 4 | } 5 | .x-blacklist-members { 6 | height: 200px; 7 | overflow-y: scroll; 8 | 9 | .force-overflow { 10 | min-height: 230px; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /demo/src/components/chat/Audio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by clock on 2017/8/28. 3 | */ 4 | import React from 'react' 5 | import { Button, Icon } from 'antd' 6 | 7 | class Audio extends React.Component { 8 | constructor() { 9 | super() 10 | this.play = this.play.bind(this) 11 | } 12 | state = { 13 | length: 0 14 | } 15 | play() { 16 | this.refs.aud.play() 17 | } 18 | componentDidMount() { 19 | if (this.props.length === 0) { 20 | let audio = this.refs.aud 21 | audio.oncanplay = () => { 22 | this.setState({ length: audio.duration }) 23 | } 24 | } 25 | 26 | } 27 | render() { 28 | let url = this.props.url 29 | return ( 30 | 35 | ) 36 | } 37 | } 38 | 39 | export default Audio 40 | -------------------------------------------------------------------------------- /demo/src/components/chat/ChatEmoji.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Menu, Icon, Dropdown } from 'antd' 4 | import emoji from '@/config/emoji' 5 | import './style/ChatEmoji.less' 6 | 7 | const SubMenu = Menu.SubMenu 8 | const MenuItem = Menu.Item 9 | const path = emoji.path 10 | 11 | class ChatEmoji extends React.Component { 12 | state = { 13 | tabPosition: 'bottom', 14 | size: '', 15 | emojiPadding: 5, 16 | emojiWidth: 25, 17 | lineNum: 10 18 | } 19 | 20 | renderEmojiMenu() { 21 | const { emojiWidth, emojiPadding, lineNum } = this.state 22 | const emojisNum = Object.values(emoji.map).length 23 | const rows = Math.ceil(emojisNum / lineNum) 24 | const width = (emojiWidth + 2 * emojiPadding) * lineNum 25 | const height = (emojiWidth + 2 * emojiPadding) * rows 26 | 27 | return ( 28 | 29 | {this.renderEmoji()} 30 | {/* 31 | 32 |

console.log(e)}> 33 | 123 34 |

35 |
36 | */} 37 |
38 | ) 39 | } 40 | 41 | renderEmoji() { 42 | // console.log(emojis) 43 | const { emojiWidth, emojiPadding, lineNum } = this.state 44 | 45 | return Object.keys(emoji.map).map((k, index) => { 46 | const v = emoji.map[k] 47 | return ( 48 | 57 | 62 | 63 | ) 64 | }) 65 | } 66 | 67 | handleChange = tabPosition => { 68 | // this.setState({ tabPosition }) 69 | } 70 | render() { 71 | const menu = this.renderEmojiMenu() 72 | 73 | return ( 74 |
75 | 76 | 77 | {/**/} 78 | 82 | 83 | 84 |
85 | ) 86 | } 87 | } 88 | 89 | export default ChatEmoji; 90 | -------------------------------------------------------------------------------- /demo/src/components/chat/style/ChatEmoji.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; 2 | @x-emoji-prefix-cls: ~"x-emoji"; 3 | 4 | .@{x-emoji-prefix-cls} { 5 | box-shadow: 0 0 4px 0 rgba(173, 185, 193, 0.5); 6 | // box-sizing: content-box; 7 | // padding: 23px 25px 0; 8 | 9 | .ant-dropdown-menu-item-disabled ant-dropdown-menu-item { 10 | cursor: pointer; 11 | } 12 | 13 | .ant-dropdown-menu-item { 14 | // width: 50px; 15 | padding: 5px; 16 | box-sizing: content-box; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/components/chat/style/ChatMessage.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; 2 | 3 | @x-message-prefix-cls: ~"x-message"; 4 | 5 | .@{x-message-prefix-cls} { 6 | &-group { 7 | padding-top: 3px; 8 | font-size: @font-size-lg; 9 | line-height: @line-height-base; 10 | margin-top: 10px; 11 | } 12 | 13 | &-user { 14 | font-size: @font-size-lg; 15 | line-height: @line-height-base; 16 | color: #1c2238; 17 | } 18 | 19 | &-content { 20 | } 21 | 22 | &-text { 23 | display: inline-block; 24 | padding: 13px 15px; 25 | border-radius: 25px; 26 | border: 1px solid #eceff1; 27 | min-width: 100px; 28 | word-break: break-all; 29 | margin-bottom: 4px; 30 | } 31 | 32 | &-img { 33 | max-width: 60%; 34 | } 35 | 36 | &-video { 37 | max-width: 60%; 38 | } 39 | 40 | &-audio { 41 | // max-width: 60%; 42 | button { 43 | width: 100px; 44 | text-align: left; 45 | font-size: 17px; 46 | } 47 | } 48 | 49 | &-idCard{ 50 | border: 1px solid #ccc; 51 | height: 60px; 52 | width: 180px; 53 | display: flex; 54 | align-items: center; 55 | cursor: pointer; 56 | background: #e8e8e8; 57 | 58 | div:nth-child(1){ 59 | width: 50px; 60 | height: 50px; 61 | background: #fff; 62 | margin: 5px; 63 | border-radius: 25px; 64 | } 65 | div:nth-child(2){ 66 | display: flex; 67 | flex: 1; 68 | overflow: hidden; 69 | } 70 | } 71 | &-idCard-right{ 72 | float: right; 73 | } 74 | &-file { 75 | overflow: hidden; 76 | h3 { 77 | max-width: 100%; 78 | font-size: 15px; 79 | height: 20px; 80 | line-height: 20px; 81 | font-weight: 500; 82 | text-overflow: ellipsis; 83 | overflow: hidden; 84 | white-space: nowrap; 85 | text-align: left; 86 | margin-bottom: 20px; 87 | } 88 | p { 89 | color: #999; 90 | text-align: left; 91 | } 92 | a { 93 | color: #999999; 94 | text-align: right; 95 | display: block; 96 | } 97 | a:hover { 98 | text-decoration: underline; 99 | } 100 | } 101 | 102 | &-time { 103 | margin-left: 2px; 104 | margin-top: 3px; 105 | font-size: @font-size-base; 106 | color: #888c98; 107 | // font-weight: lighter; 108 | } 109 | 110 | &-status { 111 | margin: 0 15px; 112 | } 113 | 114 | &-right { 115 | text-align: right; 116 | 117 | .@{x-message-prefix-cls} { 118 | &-img { 119 | float: right; 120 | } 121 | 122 | &-content { 123 | overflow: hidden; 124 | } 125 | &-text { 126 | float: right; 127 | max-width: 80%; 128 | text-align: left; 129 | background: #eceff1; 130 | word-wrap: break-word; 131 | } 132 | &-user { 133 | text-align: right; 134 | display: none; 135 | } 136 | &-time { 137 | text-align: right; 138 | } 139 | &-status { 140 | } 141 | } 142 | .ant-card { 143 | float: right; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /demo/src/components/chat/style/ReplyMessage.less: -------------------------------------------------------------------------------- 1 | .message-reply-container{ 2 | display: flex; 3 | align-items: center; 4 | .message-close{ 5 | transform: rotate(45deg); 6 | display: inline-block; 7 | margin: 0 4px 0 10px; 8 | cursor: pointer; 9 | } 10 | } 11 | .message-reply{ 12 | bottom: -10px; 13 | /* width: fit-content; */ 14 | right: 0; 15 | color: #666666; 16 | font-size: 12px; 17 | line-height: 20px; 18 | background: #F0F0F0; 19 | border-radius: 16px; 20 | padding: 4px 8px; 21 | // margin-top: -12px; 22 | 23 | display: flex; 24 | align-items: center; 25 | overflow: hidden; 26 | 27 | .msg-file{ 28 | color: #999999; 29 | :hover{ 30 | color: #00ba6e; 31 | } 32 | } 33 | .reply-txt{ 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | display: -webkit-box; 37 | text-align: left; 38 | // max-height: 40px; 39 | word-break: break-all; 40 | display: -webkit-box; 41 | /*! autoprefixer: off */ 42 | -webkit-box-orient: vertical!important; 43 | /*! autoprefixer: on */ 44 | -webkit-line-clamp: 2; 45 | } 46 | } -------------------------------------------------------------------------------- /demo/src/components/chat/style/index.less: -------------------------------------------------------------------------------- 1 | @import "./ChatMessage.less"; 2 | -------------------------------------------------------------------------------- /demo/src/components/common/AppliedRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route } from 'react-router-dom' 3 | 4 | export default ({ component: C, props: cProps, ...rest }) => 5 | } /> 6 | -------------------------------------------------------------------------------- /demo/src/components/common/AsyncComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { I18n } from 'react-redux-i18n' 3 | import Loadable from 'react-loadable' 4 | import NProgress from 'nprogress' 5 | 6 | export default function MyLoadable(opts) { 7 | return Loadable( 8 | Object.assign( 9 | { 10 | loading: MyLoadingComponent, 11 | delay: 200, 12 | timeout: 5000, 13 | render(loaded, props) { 14 | NProgress.done() 15 | let Component = loaded.default 16 | return 17 | } 18 | }, 19 | opts 20 | ) 21 | ) 22 | } 23 | 24 | function MyLoadingComponent(props) { 25 | NProgress.start() 26 | if (props.isLoading) { 27 | // While our other component is loading... 28 | if (props.timedOut) { 29 | // In case we've timed out loading our other component. 30 | return ( 31 |
32 | {I18n.t('LoadTimeout')} 33 |
34 | ) 35 | } else if (props.pastDelay) { 36 | // Display a loading screen after a set delay. 37 | NProgress.inc() 38 | // return
Loading...
39 | return null 40 | } else { 41 | NProgress.done() 42 | // Don't flash "Loading..." when we don't need to. 43 | return null 44 | } 45 | } else if (props.error) { 46 | NProgress.done() 47 | // If we aren't loading, maybe 48 | return ( 49 |
50 | {I18n.t('loadFailded')} 51 |
52 | ) 53 | } else { 54 | NProgress.done() 55 | // This case shouldn't happen... but we'll return null anyways. 56 | return null 57 | } 58 | } 59 | 60 | // Loadable.Map({ 61 | // loader: { 62 | // Component: () => import('./my-component'), 63 | // translations: () => fetch('./foo-translations.json').then(res => res.json()), 64 | // }, 65 | // render(loaded, props) { 66 | // let Component = loaded.Component.default; 67 | // let translations = loaded.translations; 68 | // return ; 69 | // } 70 | // }); 71 | -------------------------------------------------------------------------------- /demo/src/components/common/AsyncComponentBak.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | export default function asyncComponent(importComponent) { 4 | class AsyncComponent extends Component { 5 | constructor(props) { 6 | super(props) 7 | 8 | this.state = { 9 | component: null 10 | } 11 | } 12 | 13 | async componentDidMount() { 14 | const { default: component } = await importComponent() 15 | 16 | this.setState({ 17 | component: component 18 | }) 19 | } 20 | 21 | render() { 22 | const C = this.state.component 23 | 24 | return C ? : null 25 | } 26 | } 27 | 28 | return AsyncComponent 29 | } 30 | -------------------------------------------------------------------------------- /demo/src/components/common/EditInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Input, Button, Icon, message } from 'antd' 3 | export default class EditInput extends React.Component{ 4 | constructor(props){ 5 | super(props) 6 | this.state = { 7 | showEdit: true, 8 | spanValue: props.value || '', 9 | inputValue: props.value || '' 10 | } 11 | } 12 | 13 | handleChange = (e) => { 14 | const value = e.target.value || ""; 15 | this.setState({ 16 | inputValue: value 17 | }); 18 | }; 19 | 20 | handleClick = () => { 21 | let verifyResult = true 22 | let reg 23 | switch(this.props.type){ 24 | case 'birth': 25 | reg = /^(19|20)\d{2}-(1[0-2]|0?[1-9])-(0?[1-9]|[1-2][0-9]|3[0-1])$/ 26 | verifyResult = reg.test(this.state.inputValue) 27 | break; 28 | case 'gender': 29 | reg = /^(男|女|未知)$/ 30 | verifyResult = reg.test(this.state.inputValue) 31 | break; 32 | case 'phone': 33 | reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/ 34 | verifyResult = reg.test(this.state.inputValue) 35 | break; 36 | case 'mail': 37 | reg=/^\w+@[a-zA-Z0-9]{2,10}(?:\.[a-z]{2,4}){1,3}$/; 38 | verifyResult = reg.test(this.state.inputValue) 39 | break 40 | default: 41 | break; 42 | } 43 | if (!verifyResult) { 44 | return message.error('输入内容不合法') 45 | } 46 | this.setState({ 47 | spanValue: this.state.inputValue, 48 | showEdit: true 49 | }) 50 | this.props.onCommit(this.state.inputValue) 51 | } 52 | 53 | render(){ 54 | return( 55 |
56 | {this.props.label} 57 | 60 | {this.state.spanValue} 61 | {this.setState({ showEdit: !this.state.showEdit });}} /> 62 | 63 | 70 | 71 | 75 |
76 | ) 77 | } 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /demo/src/components/common/FadingRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Route } from 'react-router-dom' 3 | 4 | // export default ({ component: Component, ...rest }) => 5 | // 8 | // 9 | // 10 | // } 11 | // /> 12 | -------------------------------------------------------------------------------- /demo/src/components/common/LoadingComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Spin } from 'antd' 3 | 4 | const LoadingComponent = ({ size = 'large', show, msg }) => { 5 | return ( 6 |
7 |
14 | 15 | {/*

*/} 16 | {/*
{msg}...*/} 17 | {/*

*/} 18 |
19 |
20 | ) 21 | } 22 | 23 | export default LoadingComponent 24 | -------------------------------------------------------------------------------- /demo/src/components/common/ModalComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import { Modal } from 'antd' 5 | import './style/ModalComponent.less' 6 | 7 | export default class ModalComponent extends React.Component { 8 | constructor(props) { 9 | super() 10 | this.state = { visible: props.visible, title: props.title } 11 | this.handleCancel = this.handleCancel.bind(this) 12 | } 13 | 14 | showModal() { 15 | this.setState({ 16 | visible: true 17 | }) 18 | debugger 19 | } 20 | handleOk = e => { 21 | // console.log(e) 22 | this.setState({ 23 | visible: false 24 | }) 25 | } 26 | handleCancel(e) { 27 | // console.log(e) 28 | this.setState({ 29 | visible: false 30 | }) 31 | if (typeof this.props.onModalClose === 'function') this.props.onModalClose() 32 | } 33 | 34 | onChangeTitle = v => { 35 | this.setState({ 36 | title: v 37 | }) 38 | } 39 | 40 | componentWillReceiveProps(nextProps) { 41 | this.setState({ 42 | visible: nextProps.visible, 43 | title: nextProps.title 44 | }) 45 | } 46 | 47 | render() { 48 | if (!this.state.visible) { 49 | return null 50 | } 51 | 52 | return ( 53 |
54 | 65 | { 66 | 72 | } 73 | 74 |
75 | ) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /demo/src/components/common/style/ModalComponent.less: -------------------------------------------------------------------------------- 1 | .x-modal { 2 | &__nofooter { 3 | .ant-modal-footer { 4 | display: none; 5 | } 6 | } 7 | .ant-modal-body { 8 | padding-bottom: 40px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /demo/src/components/contact/ContactHead.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classnames from 'classnames' 4 | 5 | const ContactHead = ({ width, imgUrl, name, className, ...rest }) => { 6 | let content = null 7 | if (name) { 8 | let names = name.split('') 9 | content = ( 10 | 11 | {names[0].toUpperCase()} 12 | 13 | ) 14 | } 15 | if (imgUrl) { 16 | content = 17 | } 18 | 19 | let size = width + 'px' 20 | 21 | return ( 22 |
31 | {content} 32 |
33 | ) 34 | } 35 | 36 | ContactHead.propTypes = { 37 | // collapse: PropTypes.bool 38 | // menuOptions: PropTypes.array.isRequired, 39 | } 40 | 41 | export default ContactHead 42 | -------------------------------------------------------------------------------- /demo/src/components/contact/ContactItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { Menu, Icon, Badge, Avatar } from 'antd' 4 | import ContactHead from './ContactHead' 5 | import WebIM from '@/config/WebIM' 6 | import { MENTION_ALL } from '@/const/' 7 | 8 | const SubMenu = Menu.SubMenu 9 | const MenuItemGroup = Menu.ItemGroup 10 | const defaultAvatar = 'https://download-sdk.oss-cn-beijing.aliyuncs.com/downloads/IMDemo/avatar/Image1.png' 11 | 12 | 13 | 14 | const ContactItem = ({ chatType, items, collapse, hasLogo, mentionedGroupIdLit, removeMentionedGroupId, ...rest }) => { 15 | const tabs = items //["Contacts", "Chat", "Public"] 16 | const tabsLen = tabs.length 17 | const tabCls = collapse ? '' : '' 18 | 19 | // 清除群组提醒提示 20 | const clearGroupMentioned = (item)=>{ 21 | if(chatType === 'group'){ 22 | removeMentionedGroupId({ groupId: item.id }) 23 | } 24 | } 25 | 26 | 27 | const tabsItem = tabs.map(item => 28 | { clearGroupMentioned(item) }} key={chatType == 'chatroom' || chatType == 'group' ? item.id : item.name} className={tabCls} style={{ margin:0 }}> 29 | {hasLogo ? : ''} 30 |
31 |
32 | {chatType == 'contact' ? 33 | :null} 34 | {item?.info?.nickname || item.name} 35 | {/* 36 | 44 | */} 45 | {/* {chatType === "group" ? : ""} */} 46 | 47 |
48 |
49 | {mentionedGroupIdLit.includes(item.id) ? [@Mention me] : ''} 50 | {item.latestMessage} 51 |
52 |
53 |
54 | {item.latestTime} 55 |
56 |
57 | ) 58 | 59 | return ( 60 | 61 | {tabsItem} 62 | 63 | ) 64 | } 65 | 66 | ContactItem.propTypes = { 67 | collapse: PropTypes.bool 68 | // menuOptions: PropTypes.array.isRequired, 69 | } 70 | 71 | export default ContactItem 72 | -------------------------------------------------------------------------------- /demo/src/components/friend/AddFriendsModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import { connect } from 'react-redux' 5 | import { I18n } from 'react-redux-i18n' 6 | import { Modal, Input, Button, Row, Col } from 'antd' 7 | import WebIM from '@/config/WebIM' 8 | import RosterActions from '@/redux/RosterRedux' 9 | 10 | class AddFriendsModal extends React.Component { 11 | constructor(props) { 12 | super() 13 | } 14 | 15 | state = { 16 | userName: '' 17 | } 18 | 19 | add = () => { 20 | const value = this.state.userName 21 | if (!value || !value.trim()) return 22 | this.props.addContact(value) 23 | this.props.onCancel && this.props.onCancel() 24 | } 25 | 26 | onChangeUserName = e => { 27 | this.setState({ userName: e.target.value }) 28 | } 29 | 30 | render() { 31 | return ( 32 | 33 | 34 | (this.userNameInput = node)} 39 | /> 40 | 41 | 42 | 52 | 53 | 54 | ) 55 | } 56 | } 57 | 58 | export default connect( 59 | ({ state }) => ({}), 60 | dispatch => ({ 61 | addContact: id => dispatch(RosterActions.addContact(id)) 62 | }) 63 | )(AddFriendsModal) 64 | -------------------------------------------------------------------------------- /demo/src/components/friend/FriendsRequestModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import { connect } from 'react-redux' 5 | import { Modal, Input, Button, Row, Col } from 'antd' 6 | import WebIM from '@/config/WebIM' 7 | import SubscribeActions from '@/redux/SubscribeRedux' 8 | import { I18n } from 'react-redux-i18n' 9 | import _ from 'lodash' 10 | 11 | class FriendsRequestModal extends React.Component { 12 | state = {} 13 | 14 | render() { 15 | const { declineSubscribe, acceptSubscribe } = this.props 16 | const requests = [] 17 | 18 | _.forEach(this.props.data, ({ from, status }) => { 19 | requests.push( 20 | 21 | 22 | {from + ': ' + status} 23 | 24 | 25 | 36 | 46 | 47 | 48 | ) 49 | }) 50 | 51 | return ( 52 |
53 | {requests} 54 |
55 | ) 56 | } 57 | } 58 | 59 | export default connect( 60 | ({ entities }) => ({ 61 | data: entities.subscribe.byFrom 62 | }), 63 | dispatch => ({ 64 | acceptSubscribe: id => dispatch(SubscribeActions.acceptSubscribe(id)), 65 | declineSubscribe: id => dispatch(SubscribeActions.declineSubscribe(id)) 66 | }) 67 | )(FriendsRequestModal) 68 | -------------------------------------------------------------------------------- /demo/src/components/group/GroupInviteModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import { connect } from 'react-redux' 5 | import { Modal, Input, Button, Row, Col } from 'antd' 6 | import WebIM from '@/config/WebIM' 7 | import { I18n } from 'react-redux-i18n' 8 | import _ from 'lodash' 9 | import GroupInviteActions from '@/redux/GroupInviteRedux' 10 | import GroupActions from '@/redux/GroupRedux' 11 | class GroupInviteModal extends React.Component { 12 | state = { 13 | toNick: '', 14 | groupName: '', 15 | reason: '' 16 | } 17 | onRefuse = (gid) => { 18 | var me = this 19 | var options = { 20 | invitee: WebIM.conn.user, 21 | groupId: gid, 22 | success: function(resp) { 23 | me.props.upDateGroupList() 24 | }, 25 | error: function(e) {} 26 | } 27 | this.props.rejectInviteIntoGroup(gid, options) 28 | } 29 | 30 | onAgree = (gid) => { 31 | var me = this 32 | var options = { 33 | groupId: gid, 34 | invitee: WebIM.conn.user, 35 | success: function(resp) { 36 | me.props.upDateGroupList() 37 | }, 38 | error: function(e) {} 39 | } 40 | this.props.agreeInviteIntoGroup(gid, options) 41 | } 42 | 43 | render() { 44 | console.log('this.props.groupRequests', this.props.groupRequests) 45 | // 过滤一下groupRequests的id,防止相同的进群邀请 46 | const requests = [] 47 | _.forEach(this.props.groupRequests, val => { 48 | _.forEach(val, ({ from, status, toNick, reason, gid }) => { 49 | requests.push( 50 | 51 | 52 | {`${from}${I18n.t('inviteIntoGroup')}${gid}`} 53 |

54 | {reason} 55 |

56 | 57 | 58 | 69 | 79 | 80 |
81 | ) 82 | }) 83 | }) 84 | 85 | return ( 86 |
87 | {requests} 88 |
89 | ) 90 | } 91 | } 92 | 93 | export default connect( 94 | ({ entities }) => ({ 95 | groupRequests: entities.groupRequest.byGid 96 | }), 97 | dispatch => ({ 98 | agreeInviteIntoGroup: (gid, options) => dispatch(GroupInviteActions.agreeInviteIntoGroup(gid, options)), 99 | rejectInviteIntoGroup: (gid, options) => dispatch(GroupInviteActions.rejectInviteIntoGroup(gid, options)), 100 | upDateGroupList: (gid, options) => dispatch(GroupActions.getGroups()) 101 | }) 102 | )(GroupInviteModal) 103 | -------------------------------------------------------------------------------- /demo/src/components/group/GroupRequestModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | import { connect } from 'react-redux' 5 | import { Modal, Input, Button, Row, Col } from 'antd' 6 | import WebIM from '@/config/WebIM' 7 | import { I18n } from 'react-redux-i18n' 8 | import _ from 'lodash' 9 | import GroupRequestActions from '@/redux/GroupRequestRedux' 10 | 11 | class GroupRequestModal extends React.Component { 12 | state = { 13 | toNick: '', 14 | groupName: '', 15 | reason: '' 16 | } 17 | onRefuse = (gid, applicant) => { 18 | // const { from } = this.props.groupRequests[gid] || {} 19 | // const request = _.get(this.props.groupRequests, from, {}) 20 | var options = { 21 | applicant: applicant, 22 | groupId: gid, 23 | success: function(resp) { 24 | console.log(resp) 25 | }, 26 | error: function(e) {} 27 | } 28 | this.props.rejectJoinGroup(gid, options) 29 | } 30 | 31 | onAgree = (gid, applicant) => { 32 | // const { from } = this.props.groupRequests[gid] || {} 33 | var options = { 34 | groupId: gid, 35 | applicant, 36 | success: function(resp) { 37 | console.log(resp) 38 | }, 39 | error: function(e) {} 40 | } 41 | this.props.agreeJoinGroup(gid, options) 42 | } 43 | 44 | render() { 45 | const requests = [] 46 | 47 | _.forEach(this.props.groupRequests, val => { 48 | _.forEach(val, ({ from, status, toNick, reason, gid }) => { 49 | requests.push( 50 | 51 | 52 | {`${from}${I18n.t('joinGroup')}`} 53 |

54 | {reason} 55 |

56 | 57 | 58 | 69 | 79 | 80 |
81 | ) 82 | }) 83 | }) 84 | 85 | return ( 86 |
87 | {requests} 88 |
89 | ) 90 | } 91 | } 92 | 93 | export default connect( 94 | ({ entities }) => ({ 95 | groupRequests: entities.groupRequest.byGid 96 | }), 97 | dispatch => ({ 98 | agreeJoinGroup: (gid, options) => dispatch(GroupRequestActions.agreeJoinGroup(gid, options)), 99 | rejectJoinGroup: (gid, options) => dispatch(GroupRequestActions.rejectJoinGroup(gid, options)) 100 | }) 101 | )(GroupRequestModal) 102 | -------------------------------------------------------------------------------- /demo/src/components/group/style/AddGrouModal.less: -------------------------------------------------------------------------------- 1 | .a-add{ 2 | display: flex; 3 | } 4 | .x-add-group { 5 | .ant-checkbox-group-item { 6 | display: block; 7 | } 8 | .x-add-group-members { 9 | max-height: 300px; 10 | overflow: scroll; 11 | } 12 | } 13 | 14 | .x-add-container{ 15 | width: 50%; 16 | height: 5rem; 17 | border-right: 1px solid #F2F2F2; 18 | padding-right: 5px; 19 | overflow-y: scroll; 20 | .x-add-header { 21 | border-bottom: 1px solid #F2F2F2; 22 | padding-bottom: 10px; 23 | } 24 | } 25 | 26 | .x-add-selected{ 27 | width: 50%; 28 | padding-left: 12px; 29 | position: relative; 30 | display: flex; 31 | flex-direction: column; 32 | .x-add-selected-text{ 33 | text-align: right; 34 | } 35 | 36 | .btn-container{ 37 | display: flex; 38 | justify-content: space-between; 39 | position: absolute; 40 | bottom: 0; 41 | width: 100%; 42 | button{ 43 | width: 45%; 44 | } 45 | button:first-of-type{ 46 | color: #3A3A3A; 47 | } 48 | } 49 | .selected-members{ 50 | flex: 1; 51 | margin-bottom: 35px; 52 | overflow-y: scroll; 53 | } 54 | } 55 | 56 | .a-add-members{ 57 | padding: 0; 58 | li { 59 | height: 0.5rem; 60 | border-bottom: 1px solid #F2F2F2; 61 | list-style: none; 62 | display: flex; 63 | justify-content: space-between; 64 | align-items: center; 65 | img{ 66 | width: 0.4rem; 67 | height: 0.4rem; 68 | margin-right: 6px; 69 | } 70 | div{ 71 | font-size: 14px; 72 | color: #424242; 73 | } 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /demo/src/components/group/style/JoinGroupModal.less: -------------------------------------------------------------------------------- 1 | .x-join-group { 2 | .ant-checkbox-group-item { 3 | display: block; 4 | } 5 | .x-join-group-members { 6 | height: 250px; 7 | overflow: scroll; 8 | } 9 | 10 | .x-list-item { 11 | padding: 0; 12 | cursor: pointer; 13 | font-size: 14px; 14 | font-weight: bold; 15 | height: 40px; 16 | line-height: 40px; 17 | } 18 | 19 | .icon-users-1 { 20 | float: right; 21 | line-height: 40px; 22 | } 23 | 24 | .title { 25 | display: block; 26 | font-size: 14px; 27 | font-weight: bold; 28 | margin-top: 5px; 29 | } 30 | .content { 31 | display: block; 32 | font-size: 14px; 33 | color: #888c98; 34 | margin-top: 5px; 35 | margin-bottom: 20px; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/src/components/group/style/index.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件 2 | 3 | .gray { 4 | color: #cccccc; 5 | } 6 | 7 | .fs-117em { 8 | font-size: 1.17em; 9 | } 10 | 11 | .group-member-wrapper { 12 | .ant-card-body { 13 | padding: 0; 14 | } 15 | } 16 | 17 | .group-member-list { 18 | // height: 280px; 19 | // overflow-y: scroll; 20 | display: absolute; 21 | top: 0; 22 | bottom: 0; 23 | 24 | .ant-table { 25 | width: 100%; 26 | } 27 | } 28 | 29 | .group-member-item { 30 | font-size: 1.17em; 31 | color: black; 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/components/header/HeaderTab.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { Menu, Icon, Badge } from 'antd' 5 | import _ from 'lodash' 6 | import './style/HeaderTab.less' 7 | 8 | const SubMenu = Menu.SubMenu 9 | const MenuItemGroup = Menu.ItemGroup 10 | 11 | class HeaderTab extends React.Component { 12 | render() { 13 | const { collapsed, items, unread, ...rest } = this.props 14 | const typeMap = { contact: 'chat', group: 'groupchat', chatroom: 'chatroom', stranger: 'stranger' } 15 | const hadUnread = { contact: false, group: false, chatroom: false, stranger: false } 16 | _.forEach(typeMap, (v, k) => { 17 | const m = _.get(unread, v) 18 | if (!_.isEmpty(m)) hadUnread[k] = true 19 | }) 20 | 21 | const tabs = items || {} 22 | const tabsLen = tabs.length 23 | const tabCls = collapsed ? `ant-col-${Math.floor(24 / tabsLen)}` : '' 24 | 25 | const tabsItem = tabs.map(({ key, name, icon }) => 26 | 27 | {/**/} 28 | {hadUnread[key] 29 | ? 30 | 34 | 35 | : } 36 | {collapsed 37 | ? '' 38 | : 39 | {name} 40 | } 41 | 42 | ) 43 | 44 | // const tabsColItem = ( 45 | // } className="collapsed"> 46 | // {tabsItem} 47 | // 48 | // ) 49 | 50 | return ( 51 | 52 | {tabsItem} 53 | 54 | ) 55 | } 56 | } 57 | 58 | HeaderTab.propTypes = { 59 | collapsed: PropTypes.bool 60 | // menuOptions: PropTypes.array.isRequired, 61 | } 62 | 63 | 64 | export default connect( 65 | ({ entities }, props) => ({ 66 | unread: entities.message.unread, 67 | ...props 68 | }), 69 | dispatch => ({}) 70 | )(HeaderTab) 71 | -------------------------------------------------------------------------------- /demo/src/components/header/style/HeaderOps.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件 2 | 3 | .x-header-ops__dropmenu { 4 | padding: 10px 0px; 5 | 6 | .ant-dropdown-menu-item { 7 | padding-left: 23px; 8 | padding-right: 23px; 9 | font-size: 14px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/src/components/header/style/HeaderTab.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件 2 | 3 | .x-header-tab__menu { 4 | .x-header-tab__badge { 5 | margin-right: 21px; 6 | 7 | .ant-badge-dot { 8 | box-shadow: 0 0px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/src/components/header/style/index.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件 2 | 3 | @menu-prefix-cls: ~"@{ant-prefix}-menu"; 4 | 5 | #x-header-tab.@{menu-prefix-cls} { 6 | text-align: center; 7 | // width: 350px; 8 | background-color: #000; 9 | color: #adb9c1; 10 | height: 50px; 11 | 12 | &-horizontal &-item, 13 | &-horizontal &-submenu { 14 | top: 0; 15 | } 16 | 17 | &-horizontal { 18 | > .@{menu-prefix-cls}-item { 19 | border-bottom-width: 3px; 20 | background-color: #000; 21 | margin-top: 0; 22 | top: -1px; 23 | 24 | &:hover, 25 | &-active, 26 | &-open, 27 | &-selected { 28 | // border-bottom-width: 3px; 29 | // border-bottom: 1px solid transparent; 30 | color: #fff; 31 | // background-color: #00ba6e; 32 | } 33 | } 34 | } 35 | 36 | .@{menu-prefix-cls}-item { 37 | box-sizing: border-box; 38 | padding: 0 22px; 39 | line-height: 46px; 40 | } 41 | 42 | // &-item, 43 | // &-submenu-title { 44 | // } 45 | 46 | .anticon { 47 | margin-right: 0; 48 | vertical-align: middle; 49 | } 50 | 51 | .collapse { 52 | width: 100%; 53 | text-align: left; 54 | } 55 | 56 | .nav-text { 57 | font-size: 14px; 58 | vertical-align: middle; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /demo/src/components/list/ListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import classNames from 'classnames' 4 | 5 | let key = 0 6 | 7 | const ListItem = ({ config, className, ...rest }) => { 8 | !config && (config = []) 9 | 10 | const modes = { 11 | left: 'fl', 12 | right: 'fr', 13 | inlineBlock: 'ib', 14 | block: '' 15 | } 16 | 17 | const content = config.map(conf => { 18 | key++ 19 | 20 | return ( 21 |
22 | {conf.component()} 23 |
24 | ) 25 | }) 26 | 27 | return ( 28 |
29 | {content} 30 |
31 | ) 32 | } 33 | 34 | ListItem.propTypes = { 35 | // collapse: PropTypes.bool 36 | // menuOptions: PropTypes.array.isRequired, 37 | } 38 | 39 | export default ListItem 40 | -------------------------------------------------------------------------------- /demo/src/components/list/style/index.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; // 引入官方提供的 less 样式入口文件 2 | 3 | .fl { 4 | float: left; 5 | } 6 | .fr { 7 | float: right; 8 | } 9 | .ib { 10 | display: inline-block; 11 | } 12 | .x-list-item { 13 | outline: none; 14 | margin-bottom: 0; 15 | padding-left: 0; // Override default ul/ol 16 | list-style: none; 17 | z-index: @zindex-dropdown; 18 | // box-shadow: @box-shadow-base; 19 | color: @text-color; 20 | background: @component-background; 21 | line-height: 46px; 22 | // cursor: pointer; 23 | transition: all .3s; 24 | padding: 0 16px; 25 | font-size: @font-size-base; 26 | line-height: 42px; 27 | height: auto; 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | 31 | // &:hover, 32 | // &-active { 33 | // background-color: transparent; 34 | // color: @primary-color; 35 | // } 36 | } 37 | -------------------------------------------------------------------------------- /demo/src/components/recorder/index.less: -------------------------------------------------------------------------------- 1 | .recoder{ 2 | /* position: fixed; 3 | left: calc(50% - 100px); 4 | top: calc(50% - 100px); */ 5 | position: relative; 6 | box-shadow: 0 0 32px rgba(0, 0, 0, 0.15); 7 | width: 200px; 8 | height: 200px; 9 | background: #fff; 10 | } 11 | .recorderWraper{ 12 | font-size: 20px; 13 | display: inline-block; 14 | padding: 0 10px; 15 | cursor: pointer; 16 | color: rgba(0, 0, 0, 0.65); 17 | } 18 | .sound-waves{ 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | height: 70px; 23 | } 24 | .wavesItem{ 25 | border: 1px solid #f2f2f2; 26 | width: 4px; 27 | background: green; 28 | margin: 2px; 29 | } 30 | .tipText{ 31 | text-align: center; 32 | font-size: 16px; 33 | color: #757575; 34 | bottom: 70px; 35 | width: 100%; 36 | } 37 | .holdBtn{ 38 | width: 50px; 39 | height: 50px; 40 | border-radius: 25px; 41 | text-align: center; 42 | padding: 0; 43 | border-color: #e2e2e2; 44 | background: rgb(255, 255, 255); 45 | } 46 | .microphone{ 47 | height: 50px; 48 | width: 30px; 49 | margin: 0 !important; 50 | } -------------------------------------------------------------------------------- /demo/src/components/videoCall/AlertModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { connect } from 'react-redux'; 4 | import { message, Modal } from 'antd'; 5 | import avatar from '@/themes/img/avatar@2x.png' 6 | import accept from '@/themes/img/acceptCall@2x.png' 7 | import hangup from '@/themes/img/hangupCall@2x.png' 8 | import VideoCallActions from '@/redux/VideoCallRedux'; 9 | import GroupMemberActions from '@/redux/GroupMemberRedux'; 10 | class AtertModal extends React.Component { 11 | constructor(){ 12 | super() 13 | } 14 | 15 | componentDidMount(){ 16 | this.timer = setTimeout(()=>{ 17 | this.refuse() 18 | }, 30000) 19 | } 20 | accept(){ 21 | const answerCallStatus = 5 22 | this.props.answerCall('accept') 23 | this.props.setCallStatus(answerCallStatus) 24 | // this.props.getGroupMember(this.props.gid) 25 | if(this.props.gid){ 26 | this.props.listGroupMemberAsync({ groupId: this.props.gid }) 27 | } 28 | clearTimeout(this.timer) 29 | } 30 | 31 | refuse(){ 32 | this.props.answerCall('refuse') 33 | if (this.props.callStatus < 7) { //拒接 34 | this.props.close() 35 | } 36 | clearTimeout(this.timer) 37 | } 38 | 39 | render(){ 40 | let { type, callerIMName } = this.props.confr 41 | let text = type == 0 ? '语音': type == 1 ? '视频': '多人视频' 42 | return( 43 |
44 |
45 |
46 | 47 |
48 |
{callerIMName}
49 |
50 |
51 |
邀请你{text}通话...
52 |
53 |
54 | {this.refuse()}}/> 55 |
56 |
57 | {this.accept()}}/> 58 |
59 |
60 |
61 |
62 | ) 63 | } 64 | } 65 | 66 | 67 | export default connect( 68 | (state, props) => ({ 69 | callStatus: state.callVideo.callStatus, 70 | confr: state.callVideo.confr, 71 | gid: state.callVideo.gid 72 | }), 73 | (dispatch) => ({ 74 | close: () => dispatch(VideoCallActions.hangup()), 75 | setCallStatus: (status) => dispatch(VideoCallActions.setCallStatus(status)), 76 | answerCall: (result) => dispatch(VideoCallActions.answerCall(result)), 77 | cancelCall: (to) => dispatch(VideoCallActions.cancelCall(to)), 78 | getGroupMember: id => dispatch(GroupMemberActions.getGroupMember(id)), 79 | listGroupMemberAsync: opt => dispatch(GroupMemberActions.listGroupMemberAsync(opt)), 80 | }) 81 | )(AtertModal); 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /demo/src/components/videoCall/MiniModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Draggable from 'react-draggable'; 4 | import VideoCallActions from '@/redux/VideoCallRedux'; 5 | import minimodal from '@/themes/img/minimodal@2x.png' 6 | import { connect } from 'react-redux'; 7 | class MiniModal extends React.Component{ 8 | constructor(){ 9 | super() 10 | } 11 | 12 | handleClick(e){ 13 | this.props.setMinisize(false) 14 | } 15 | 16 | render(){ 17 | let time = this.props.callDuration 18 | return( 19 | 20 |
21 |

{this.handleClick(e)}}>

22 |
{time}
23 |
24 |
25 | 26 | ) 27 | } 28 | } 29 | 30 | export default connect( 31 | (state, props) => ({ 32 | callStatus: state.callVideo.callStatus, 33 | confr: state.callVideo.confr, 34 | callDuration: state.callVideo.callDuration 35 | }), 36 | (dispatch) => ({ 37 | close: () => dispatch(VideoCallActions.hangup()), 38 | setCallStatus: (status) => dispatch(VideoCallActions.setCallStatus(status)), 39 | answerCall: (result) => dispatch(VideoCallActions.answerCall(result)), 40 | cancelCall: (to) => dispatch(VideoCallActions.cancelCall(to)), 41 | setMinisize: (isMini) => dispatch(VideoCallActions.setMinisize(isMini)), 42 | }) 43 | )(MiniModal) -------------------------------------------------------------------------------- /demo/src/components/videoCall/PtopCallModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect }from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Draggable from 'react-draggable'; 4 | import { message, Modal } from 'antd'; 5 | // import VideoCallRedux from 'VideoCallRedux'; 6 | import Channel from './Channel'; 7 | import '../common/style/webrtc.less' 8 | const rtc = WebIM.rtc; 9 | const AgoraRTC = WebIM.AgoraRTC; 10 | class PtopCallModal extends React.Component { 11 | constructor(props) { 12 | super() 13 | } 14 | channel = null 15 | rtcTimeoutID = null 16 | 17 | componentDidMount(){ 18 | // this.channel = new Channel(this.refs.rtcWrapper) 19 | // this.join() 20 | // this.addListener() 21 | 22 | } 23 | componentWillReceiveProps(props){ 24 | } 25 | 26 | render() { 27 | let { minisize } = this.props 28 | let show = ([1,3,5,6,7].includes(this.props.callStatus) && typeof this.props.confr.type == 'number' &&this.props.confr.type < 2) ? true : false 29 | let classHide = minisize ? 'hide' : '' 30 | 31 | return( 32 | 33 |
34 | { 35 | show?:'' 36 | } 37 | 38 |
39 |
40 | )} 41 | } 42 | 43 | export default connect( 44 | (state, props) => ({ 45 | callStatus: state.callVideo.callStatus, 46 | confr: state.callVideo.confr, 47 | minisize: state.callVideo.minisize 48 | }), 49 | dispatch => ({}) 50 | )(PtopCallModal) 51 | 52 | 53 | -------------------------------------------------------------------------------- /demo/src/components/videoSetting/videoSettingModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Checkbox } from 'antd'; 3 | const CheckboxGroup = Checkbox.Group; 4 | 5 | 6 | class VideoSetting extends React.Component { 7 | constructor() { 8 | super() 9 | this.state={ 10 | checkedList: [] 11 | } 12 | this.onChange = this.onChange.bind(this) 13 | } 14 | 15 | componentDidMount(){ 16 | const videoSetting = JSON.parse(localStorage.getItem('videoSetting')) 17 | const checkedList = videoSetting&&Object.keys(videoSetting).filter(( key ) => { 18 | if(videoSetting[key]){ 19 | return key 20 | } 21 | }) 22 | this.setState({ 23 | checkedList 24 | }) 25 | } 26 | 27 | onChange(checkedList) { 28 | this.setState({ 29 | checkedList, 30 | }); 31 | 32 | const videoSettingObj = { 33 | recMerge: checkedList.indexOf('recMerge') != -1, 34 | rec: checkedList.indexOf('rec') != -1, 35 | } 36 | 37 | localStorage.setItem("videoSetting", JSON.stringify(videoSettingObj)) 38 | } 39 | 40 | render() { 41 | return ( 42 |
43 | 服务端录制以及录制合并设置 44 |
45 |
46 | 50 | 启用录制 51 |
52 |
53 | 启用合并 54 |
55 |
56 | ) 57 | } 58 | } 59 | 60 | export default VideoSetting 61 | -------------------------------------------------------------------------------- /demo/src/config/WebIM.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //import "script-loader!easemob-websdk/dist/strophe-1.2.8.js" 3 | /* eslint-enable */ 4 | 5 | // add dataReport sdk 6 | import websdk from './Easemob-chat-4.8.1' 7 | // import websdk from './Easemob-chat' 8 | 9 | // import websdk from 'easemob-websdk' 10 | // import webrtc from 'easemob-webrtc' 11 | // import emedia from './EMedia_sdk-dev' 12 | import config from 'WebIMConfig' 13 | import emoji from './emoji' 14 | import Api from 'axios' 15 | import { message } from 'antd' 16 | import loglevel from '@/utils/loglevel' 17 | import AgoraRTC from 'agora-rtc-sdk-ng' 18 | 19 | 20 | 21 | const rtc = { 22 | // 用来放置本地客户端。 23 | client: null, 24 | // 用来放置本地音视频频轨道对象。 25 | localAudioTrack: null, 26 | localVideoTrack: null, 27 | } 28 | 29 | console = console || {} 30 | console.group = console.group || function () { } 31 | console.groupEnd = console.groupEnd || function () { } 32 | 33 | // init DOMParser / document for strophe and sdk 34 | let WebIM = window.WebIM = {} 35 | WebIM.config = config 36 | WebIM.loglevel = loglevel 37 | WebIM.message = websdk.message 38 | WebIM.statusCode = websdk.statusCode 39 | WebIM.utils = websdk.utils 40 | WebIM.logger = websdk.logger 41 | let options = { 42 | isReport: true, 43 | isMultiLoginSessions: WebIM.config.isMultiLoginSessions, 44 | isDebug: WebIM.config.isDebug, 45 | // isReport: true, 46 | https: WebIM.config.https, 47 | isAutoLogin: false, 48 | heartBeatWait: WebIM.config.heartBeatWait, 49 | autoReconnectNumMax: WebIM.config.autoReconnectNumMax, 50 | delivery: WebIM.config.delivery, 51 | appKey: WebIM.config.appkey, 52 | useOwnUploadFun: WebIM.config.useOwnUploadFun, 53 | deviceId: WebIM.config.deviceId, 54 | //公有云 isHttpDNS 默认配置为true 55 | isHttpDNS: WebIM.config.isHttpDNS, 56 | onOffline: () => { console.log('onOffline') }, 57 | onOnline: () => { console.log('onOnline') } 58 | } 59 | 60 | // 内部沙箱测试环境 61 | // if (window.location.href.indexOf('webim-hsb-ly.easemob.com') !== -1) { 62 | // WebIM.config.isSandBox = true 63 | // } 64 | if (WebIM.config.isSandBox) { 65 | options.url = WebIM.config.socketServer // (window.location.protocol === 'https:' ? 'https:' : 'http:') + '//im-api-v2-hsb.easemob.com/ws' 66 | options.apiUrl = WebIM.config.restServer // (window.location.protocol === 'https:' ? 'https:' : 'http:') + '//a1-hsb.easemob.com' 67 | // options.url = `${window.location.protocol}//172.17.2.47:8280/ws`; 68 | // options.apiUrl = `${window.location.protocol}//172.17.2.47:8080`; 69 | options.isHttpDNS = false 70 | // WebIM.config.restServer = (window.location.protocol === "https:" ? "https:" : "http:") + '//a1-hsb.easemob.com'; 71 | } 72 | // WebIM.logger.setConfig({ 73 | // useCache: true, 74 | // maxCache: 3 * 1024 * 1024 75 | // }) 76 | WebIM.conn = new websdk.connection(options) 77 | // websdk.debug(true) 78 | 79 | 80 | 81 | const appKeyPair = WebIM.config.appkey.split('#') 82 | export let api = Api.create({ 83 | baseURL: `${WebIM.config.restServer}/${appKeyPair[0]}/${appKeyPair[1]}`, 84 | validateStatus: function (status) { 85 | return true 86 | } 87 | }) 88 | 89 | function requestFail(data) { 90 | if (data.data && data.data.error_description) { 91 | data.msg = data.data.error_description 92 | } else if (data.data && data.data.data && data.data.data.error_description) { 93 | data.msg = data.data.data.error_description 94 | } 95 | message.error('Error:' + data.status + ', ' + data.msg) 96 | return Promise.reject(data) 97 | } 98 | 99 | api.interceptors.response.use( 100 | function (resp) { 101 | if (resp.status >= 300) { 102 | return requestFail(resp) 103 | } 104 | if (resp.data && resp.data.status && resp.data.status !== 200) { 105 | return requestFail(resp.data) 106 | } 107 | if (resp.data && resp.data.data) { 108 | resp.data = resp.data.data 109 | } 110 | return resp 111 | }, 112 | function (error) { 113 | console.log(error) 114 | } 115 | ) 116 | 117 | 118 | WebIM.api = api 119 | WebIM.emoji = emoji 120 | 121 | WebIM.rtc = rtc 122 | WebIM.AgoraRTC = AgoraRTC 123 | export default WebIM 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /demo/src/config/config.js: -------------------------------------------------------------------------------- 1 | 2 | const _WIDTH = window.screen.availWidth > 350 ? 350 : window.screen.availWidth 3 | export default { 4 | // whether auto check media query and dispatch by redux or not ? 5 | reduxMatchMedia: true, 6 | // map of media query breakpoints 7 | dimensionMap: { 8 | xs: '480px', 9 | sm: '768px', 10 | md: '992px', 11 | lg: '1200px', 12 | xl: '1600px' 13 | }, 14 | name: 'Web IM', 15 | logo: '', 16 | SIDER_COL_BREAK: 'sm', //md 17 | SIDER_COL_WIDTH: 80, 18 | SIDER_WIDTH: 350, 19 | RIGHT_SIDER_WIDTH: _WIDTH, 20 | imgType: { 21 | gif: 1, 22 | bmp: 1, 23 | jpg: 1, 24 | png: 1, 25 | jpeg: 1, 26 | }, 27 | 28 | PAGE_NUM: 20 29 | } 30 | -------------------------------------------------------------------------------- /demo/src/config/emoji.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | path: '../../themes/faces/', 3 | map: { 4 | '[):]': 'ee_1.png', 5 | '[:D]': 'ee_2.png', 6 | '[;)]': 'ee_3.png', 7 | '[:-o]': 'ee_4.png', 8 | '[:p]': 'ee_5.png', 9 | '[(H)]': 'ee_6.png', 10 | '[:@]': 'ee_7.png', 11 | '[:s]': 'ee_8.png', 12 | '[:$]': 'ee_9.png', 13 | '[:(]': 'ee_10.png', 14 | '[:\'(]': 'ee_11.png', 15 | '[:|]': 'ee_18.png', 16 | '[(a)]': 'ee_13.png', 17 | '[8o|]': 'ee_14.png', 18 | '[8-|]': 'ee_15.png', 19 | '[+o(]': 'ee_16.png', 20 | '[{ 11 | return( 12 |
13 | 14 |
Header
15 | Content 16 |
Footer
17 |
18 |
19 | ) 20 | } 21 | 22 | export default ChinaMobile -------------------------------------------------------------------------------- /demo/src/containers/chinamobile/style/index.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~antd/dist/antd.less"; 2 | 3 | @layout-prefix-cls-c: ~"ant-layout"; 4 | 5 | body { 6 | min-height: 100%; 7 | background-color: #ececec; 8 | position: relative; 9 | // expand height according to the content size 10 | height: auto; 11 | } 12 | 13 | .@{layout-prefix-cls-c} { 14 | overflow: auto; 15 | 16 | &-header { 17 | background-color: #837d8c; 18 | //background-color: blue; 19 | } 20 | 21 | &-content { 22 | position: absolute; 23 | left: 0; 24 | right: 0; 25 | top: 64px; 26 | bottom: 66px; 27 | background: #fff; 28 | padding: 0 16px; 29 | overflow: hidden; 30 | -webkit-overflow-scrolling: touch; 31 | -webkit-transform: translate3d(0, 0, 0); 32 | } 33 | 34 | &-footer { 35 | position: absolute; 36 | bottom: 0; 37 | left: 0; 38 | background-color: #f2f2f2; 39 | right: 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /demo/src/containers/loginregister/index.less: -------------------------------------------------------------------------------- 1 | @import (reference) '../../themes/theme.less'; 2 | 3 | .form { 4 | position: absolute; 5 | /*top: 50%; 6 | left: 50%; 7 | margin: -144px 0 0 -177.5px;*/ 8 | // width: 320px; 9 | // height: 320px; 10 | padding: 40px; 11 | // box-shadow: 0 0 100px rgba(0, 0, 0, .08); 12 | width: 355px; 13 | height: auto; 14 | border-radius: 2px; 15 | background-color: #ffffff; 16 | /*border: solid 0.5px #adb9c1;*/ 17 | 18 | button { 19 | width: 100%; 20 | } 21 | 22 | p { 23 | color: rgb(204, 204, 204); 24 | text-align: center; 25 | margin-top: 16px; 26 | 27 | span { 28 | &:first-child { 29 | margin-right: 16px; 30 | } 31 | } 32 | } 33 | } 34 | 35 | .logo { 36 | text-align: center; 37 | cursor: pointer; 38 | margin-bottom: 30px; 39 | 40 | i { 41 | width: 100%; 42 | margin-right: 8px; 43 | position: absolute; 44 | font-size: 40px; 45 | top: -100px; 46 | left: 0; 47 | right: 0; 48 | color: #3dcb91; 49 | } 50 | 51 | span { 52 | vertical-align: text-bottom; 53 | font-size: 16px; 54 | // text-transform: uppercase; 55 | display: inline-block; 56 | font-weight: bold; 57 | font-size: 18px; 58 | line-height: 1; 59 | } 60 | } 61 | 62 | .ant-spin-container, 63 | .ant-spin-nested-loading { 64 | height: 100%; 65 | } 66 | 67 | // -------- Rewrite antd input --------------- 68 | .x-login { 69 | .ant-input { 70 | padding: 13px 20px; 71 | font-size: 14px; 72 | line-height: 1; 73 | height: auto; 74 | } 75 | 76 | .has-feedback:after { 77 | top: 7px; 78 | } 79 | 80 | .ant-btn { 81 | height: auto; 82 | padding: 13px; 83 | line-height: 1; 84 | } 85 | .image-verification{ 86 | height: 45px; 87 | cursor: pointer; 88 | border: 1px solid #d9d9d9; 89 | } 90 | .extra { 91 | position: absolute; 92 | bottom: -44px; 93 | left: 0; 94 | right: 0; 95 | text-align: center; 96 | color: #707e89; 97 | font-size: 14px; 98 | 99 | span { 100 | color: @primary-color; 101 | margin-left: 5px; 102 | cursor: pointer; 103 | } 104 | 105 | } 106 | } 107 | 108 | .form-server { 109 | top: 35%; 110 | margin-left: -177.5px; 111 | width: 555px; 112 | .first-title { 113 | margin-bottom: 20px; 114 | } 115 | .switch-box { 116 | display: flex; 117 | justify-content: space-between; 118 | align-items: center; 119 | .switch-width { 120 | width: 30px; 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | // antd theme 5 | import './App.css' 6 | import './themes/theme.less' 7 | import registerServiceWorker from './registerServiceWorker' 8 | import { history } from '@/utils' 9 | 10 | import { 11 | // BrowserRouter as Router, 12 | // HashRouter as Router 13 | Router 14 | } from 'react-router-dom' 15 | // redux 16 | import { Provider } from 'react-redux' 17 | import { store } from '@/redux' 18 | import App from './App' 19 | // fix android browsers compatibilities 20 | import 'babel-polyfill' 21 | 22 | var FastClick = require('fastclick') 23 | FastClick.attach(document.body) 24 | 25 | const rootEl = document.getElementById('root') 26 | const render = Component => 27 | ReactDOM.render( 28 | 29 | {/* */} 30 | 31 | 32 | 33 | {/* */} 34 | , 35 | rootEl 36 | ) 37 | 38 | render(App) 39 | if (module.hot) module.hot.accept('./App', () => render(App)) 40 | registerServiceWorker() -------------------------------------------------------------------------------- /demo/src/layout/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | // import Sider from "./Sider" 4 | import RightSider from './RightSider' 5 | 6 | function generator(props) { 7 | return BacicComponent => { 8 | // index++ 9 | return class Adapter extends React.Component { 10 | static Header 11 | static Footer 12 | static Content 13 | // static Sider 14 | 15 | render() { 16 | const { prefixCls } = props 17 | // console.log("base", index, this.indexx) 18 | return 19 | } 20 | } 21 | } 22 | } 23 | 24 | class Basic extends React.Component { 25 | render() { 26 | const { prefixCls, className, children, ...others } = this.props 27 | let hasSider 28 | React.Children.forEach(children, element => { 29 | if (element && element.type && element.type.__ANT_LAYOUT_SIDER) { 30 | hasSider = true 31 | } 32 | }) 33 | const divCls = classNames(className, prefixCls, { 34 | [`${prefixCls}-has-sider`]: hasSider 35 | }) 36 | return ( 37 |
38 | {children} 39 |
40 | ) 41 | } 42 | } 43 | 44 | const Layout = generator({ 45 | prefixCls: 'x-layout' 46 | })(Basic) 47 | 48 | const Header = generator({ 49 | prefixCls: 'x-layout-header' 50 | })(Basic) 51 | 52 | const Footer = generator({ 53 | prefixCls: 'x-layout-footer' 54 | })(Basic) 55 | 56 | const Content = generator({ 57 | prefixCls: 'x-layout-content' 58 | })(Basic) 59 | 60 | Layout.Header = Header 61 | Layout.Footer = Footer 62 | Layout.Content = Content 63 | // Layout.Sider = Sider 64 | Layout.RightSider = RightSider 65 | 66 | export default Layout 67 | -------------------------------------------------------------------------------- /demo/src/layout/RightSider.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames' 3 | import { connect } from 'react-redux' 4 | import { I18n } from 'react-redux-i18n' 5 | import GroupInfo from '@/components/group/GroupInfo' 6 | import GroupMembers from '@/components/group/GroupMembers' 7 | import { config } from '@/config' 8 | 9 | class RightSider extends React.Component { 10 | // static defaultProps = { 11 | // prefixCls: 'x-layout-right-sider', 12 | // style: {} 13 | // }; 14 | 15 | constructor(props) { 16 | super(props) 17 | this.state = {} 18 | } 19 | 20 | componentWillReceiveProps(nextProps) {} 21 | 22 | componentDidMount() {} 23 | 24 | componentWillUnmount() {} 25 | 26 | render() { 27 | const joinPermission = { a: 'aa' } 28 | const { entities, roomId } = this.props 29 | return ( 30 |
31 | 39 | 47 |
48 | ) 49 | } 50 | } 51 | 52 | // export default RightSider; 53 | export default connect(({ entities }) => ({ entities }))(RightSider) 54 | -------------------------------------------------------------------------------- /demo/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/src/redux/BlacklistRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createReducer, createActions } from 'reduxsauce' 4 | import Immutable from 'seamless-immutable' 5 | import WebIM from '@/config/WebIM' 6 | import CommonActions from './CommonRedux' 7 | 8 | /* ------------- Types and Action Creators ------------- */ 9 | 10 | const { Types, Creators } = createActions({ 11 | updateBlacklist: [ 'list' ], 12 | // ----------------async------------------ 13 | // update black list 14 | getBlacklist: () => { 15 | return (dispatch, getState) => { 16 | WebIM.conn.getBlocklist().then(res=>{ 17 | res.data && dispatch(Creators.updateBlacklist(res.data)) 18 | }) 19 | } 20 | }, 21 | // add to black list 22 | doAddBlacklist: id => { 23 | return (dispatch, getState) => { 24 | dispatch(CommonActions.fetching()) 25 | try{ 26 | WebIM.conn.addUsersToBlocklist({name:id}); 27 | let blacklist = getState().entities.blacklist.byName.asMutable() 28 | let index = blacklist.findIndex(i=>blacklist[i]==id) 29 | if(index>-1) return 30 | blacklist.push(id); 31 | dispatch(Creators.updateBlacklist(blacklist)) 32 | }catch(e){ 33 | 34 | }finally{ 35 | dispatch(CommonActions.fetched()) 36 | } 37 | } 38 | }, 39 | // delete from blacklist 40 | doRemoveBlacklist: id => { 41 | return (dispatch, getState) => { 42 | dispatch(CommonActions.fetching()) 43 | try{ 44 | let blacklist = getState().entities.blacklist.byName.asMutable() 45 | WebIM.conn.removeUserFromBlackList({name:blacklist[id]}) 46 | blacklist.splice(id,1) 47 | dispatch(Creators.updateBlacklist(blacklist)) 48 | dispatch(CommonActions.fetched()) 49 | // delete blacklist[id] 50 | }catch(e){ 51 | dispatch(CommonActions.fetched()) 52 | } 53 | } 54 | } 55 | }) 56 | 57 | export const BlacklistTypes = Types 58 | export default Creators 59 | 60 | /* ------------- Initial State ------------- */ 61 | 62 | export const INITIAL_STATE = Immutable({ 63 | byName: {}, 64 | names: [] 65 | }) 66 | 67 | /* ------------- Reducers ------------- */ 68 | 69 | export const updateBlacklist = (state, { list }) => { 70 | return state.merge({ 71 | byName: Object(list), 72 | names: Object.keys(list).sort() 73 | }) 74 | } 75 | 76 | /* ------------- Hookup Reducers To Types ------------- */ 77 | 78 | export const reducer = createReducer(INITIAL_STATE, { 79 | [Types.UPDATE_BLACKLIST]: updateBlacklist 80 | }) 81 | 82 | /* ------------- Selectors ------------- */ 83 | -------------------------------------------------------------------------------- /demo/src/redux/ChatRoomRedux.js: -------------------------------------------------------------------------------- 1 | import { createReducer, createActions } from 'reduxsauce' 2 | import Immutable from 'seamless-immutable' 3 | import _ from 'lodash' 4 | import { WebIM } from '@/config' 5 | import { store } from '@/redux' 6 | import CommonActions from '@/redux/CommonRedux' 7 | /* ------------- Types and Action Creators ------------- */ 8 | 9 | const { Types, Creators } = createActions({ 10 | updateChatRooms: [ 'rooms' ], 11 | topChatroom: [ 'roomId' ], 12 | // ---------------async------------------ 13 | getChatRooms: () => { 14 | return (dispatch, getState) => { 15 | store.dispatch(CommonActions.getChatRoomAlready()) 16 | // console.log('getChatRooms', getState()) 17 | let pagenum = 1 18 | let pagesize = 10 19 | WebIM.conn.getChatRooms({ 20 | // apiUrl: WebIM.config.restServer, 21 | pagenum: pagenum, 22 | pagesize: pagesize, 23 | success: function(resp) { 24 | // console.log('aa', resp) 25 | resp.data && dispatch(Creators.updateChatRooms(resp.data)) 26 | }, 27 | error: function(e) {} 28 | }) 29 | } 30 | }, 31 | joinChatRoom: roomId => { 32 | return (dispatch, getState) => { 33 | WebIM.conn.joinChatRoom({ 34 | roomId: roomId 35 | }) 36 | } 37 | }, 38 | quitChatRoom: roomId => { 39 | return (dispatch, getState) => { 40 | WebIM.conn.quitChatRoom({ 41 | roomId: roomId 42 | }) 43 | } 44 | } 45 | }) 46 | 47 | export const GroupsTypes = Types 48 | export default Creators 49 | 50 | /* ------------- Initial State ------------- */ 51 | 52 | export const INITIAL_STATE = Immutable({ 53 | // byName: {}, 54 | byId: {}, 55 | names: [] 56 | }) 57 | 58 | /* ------------- Reducers ------------- */ 59 | export const updateChatRooms = (state, { rooms }) => { 60 | // let byName = {} 61 | let byId = {} 62 | let names = [] 63 | rooms.forEach(v => { 64 | // byName[v.name] = v 65 | byId[v.id] = v 66 | names.push(v.name + '_#-#_' + v.id) 67 | }) 68 | return state.merge({ 69 | byId: byId, 70 | names: names.sort() 71 | }) 72 | } 73 | 74 | export const topChatroom = (state, { roomId }) => { 75 | let names = state.getIn([ 'names' ], Immutable([])).asMutable() 76 | for (let i = 0; i < names.length; i++) { 77 | const name = names[i] 78 | if (name.split('_#-#_')[1] === roomId) { 79 | if (i === 0) return state // if already top, return directly 80 | names = _.without(names, name) 81 | names.unshift(name) 82 | break 83 | } 84 | } 85 | return state.merge({ names }) 86 | } 87 | /* ------------- Hookup Reducers To Types ------------- */ 88 | 89 | export const reducer = createReducer(INITIAL_STATE, { 90 | [Types.UPDATE_CHAT_ROOMS]: updateChatRooms, 91 | [Types.TOP_CHATROOM]: topChatroom 92 | }) 93 | 94 | /* ------------- Selectors ------------- */ 95 | -------------------------------------------------------------------------------- /demo/src/redux/CommonRedux.js: -------------------------------------------------------------------------------- 1 | import { createReducer, createActions } from 'reduxsauce' 2 | import Immutable from 'seamless-immutable' 3 | 4 | /* ------------- Types and Action Creators ------------- */ 5 | 6 | const { Types, Creators } = createActions({ 7 | fetching: [], 8 | fetched: [], 9 | getGroupAlready: null, 10 | getChatRoomAlready: null, 11 | setShowGroupRequestModal: [ 'status' ], 12 | setShowGroupInviteModal: [ 'status' ], 13 | setActiveContact: [ 'chatType', 'contact' ] 14 | }) 15 | 16 | export const CommonTypes = Types 17 | export default Creators 18 | 19 | /* ------------- Initial State ------------- */ 20 | 21 | export const INITIAL_STATE = Immutable({ 22 | fetching: false, 23 | isGetGroupAlready: false, 24 | isGetChatRoomAlready: false, 25 | showGroupRequestModal: false, 26 | showGroupInviteModal: false, 27 | activeChatType: null, 28 | activeContact: null 29 | }) 30 | 31 | /* ------------- Reducers ------------- */ 32 | 33 | export const fetching = state => { 34 | return state.merge({ fetching: true }) 35 | } 36 | 37 | export const fetched = state => { 38 | return state.merge({ fetching: false }) 39 | } 40 | 41 | export const getGroupAlready = state => { 42 | return state.merge({ isGetGroupAlready: true }) 43 | } 44 | 45 | export const getChatRoomAlready = state => { 46 | return state.merge({ isGetChatRoomAlready: true }) 47 | } 48 | 49 | export const setShowGroupRequestModal = (state, { status }) => { 50 | return state.merge({ showGroupRequestModal: status }) 51 | } 52 | 53 | export const setShowGroupInviteModal = (state, { status }) => { 54 | return state.merge({ showGroupInviteModal: status }) 55 | } 56 | 57 | export const setActiveContact = (state, { chatType, contact }) => { 58 | return state.merge({ activeChatType: chatType, activeContact: contact }) 59 | } 60 | 61 | /* ------------- Hookup Reducers To Types ------------- */ 62 | 63 | export const reducer = createReducer(INITIAL_STATE, { 64 | [Types.FETCHING]: fetching, 65 | [Types.FETCHED]: fetched, 66 | [Types.GET_GROUP_ALREADY]: getGroupAlready, 67 | [Types.GET_CHAT_ROOM_ALREADY]: getChatRoomAlready, 68 | [Types.SET_SHOW_GROUP_REQUEST_MODAL]: setShowGroupRequestModal, 69 | [Types.SET_SHOW_GROUP_INVITE_MODAL]: setShowGroupInviteModal, 70 | [Types.SET_ACTIVE_CONTACT]: setActiveContact 71 | }) 72 | 73 | /* ------------- Selectors ------------- */ 74 | -------------------------------------------------------------------------------- /demo/src/redux/ContactInfoScreenRedux.js: -------------------------------------------------------------------------------- 1 | import { createReducer, createActions } from 'reduxsauce' 2 | import Immutable from 'seamless-immutable' 3 | 4 | /* ------------- Types and Action Creators ------------- */ 5 | 6 | const { Types, Creators } = createActions({ 7 | contactDeleted: [], 8 | contactShowed: [] 9 | }) 10 | 11 | export const ContactInfoScreenTypes = Types 12 | export default Creators 13 | 14 | /* ------------- Initial State ------------- */ 15 | 16 | export const INITIAL_STATE = Immutable({ 17 | show: true, 18 | }) 19 | 20 | /* ------------- Reducers ------------- */ 21 | 22 | export const contactDeleted = (state, {}) => { 23 | return state.merge({ show: false }) 24 | } 25 | 26 | export const contactShowed = (state, {}) => { 27 | return state.merge({ show: true }) 28 | } 29 | /* ------------- Hookup Reducers To Types ------------- */ 30 | 31 | export const reducer = createReducer(INITIAL_STATE, { 32 | [Types.CONTACT_DELETED]: contactDeleted, 33 | [Types.CONTACT_SHOWED]: contactShowed, 34 | }) 35 | 36 | /* ------------- Selectors ------------- */ 37 | 38 | // Is the current user logged in? 39 | -------------------------------------------------------------------------------- /demo/src/redux/ContactsScreenRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createReducer, createActions } from 'reduxsauce' 4 | import Immutable from 'seamless-immutable' 5 | 6 | /* ------------- Types and Action Creators ------------- */ 7 | 8 | const { Types, Creators } = createActions({ 9 | nop: null 10 | }) 11 | 12 | export const ContactsTypes = Types 13 | export default Creators 14 | 15 | /* ------------- Initial State ------------- */ 16 | export const INITIAL_STATE = Immutable({}) 17 | 18 | /* ------------- Reducers ------------- */ 19 | 20 | /* ------------- Hookup Reducers To Types ------------- */ 21 | export const reducer = createReducer(INITIAL_STATE, {}) 22 | 23 | /* ------------- Selectors ------------- */ 24 | -------------------------------------------------------------------------------- /demo/src/redux/CreateStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import { autoRehydrate } from 'redux-persist' 3 | import createLogger from 'redux-logger' 4 | import Config from '../Config/DebugSettings' 5 | import createSagaMiddleware from 'redux-saga' 6 | import R from 'ramda' 7 | import RehydrationServices from '../Services/RehydrationServices' 8 | import ReduxPersist from '../Config/ReduxPersist' 9 | // 10 | import thunkMiddleware from 'redux-thunk' 11 | 12 | // creates the store 13 | export default (rootReducer, rootSaga) => { 14 | /* ------------- Redux Configuration ------------- */ 15 | 16 | const middleware = [] 17 | const enhancers = [] 18 | 19 | middleware.push(thunkMiddleware) 20 | 21 | /* ------------- Saga Middleware ------------- */ 22 | 23 | // const sagaMiddleware = createSagaMiddleware() 24 | // middleware.push(sagaMiddleware) 25 | 26 | /* ------------- Logger Middleware ------------- */ 27 | 28 | const SAGA_LOGGING_BLACKLIST = [ 'EFFECT_TRIGGERED', 'EFFECT_RESOLVED', 'EFFECT_REJECTED', 'persist/REHYDRATE' ] 29 | if (__DEV__) { 30 | // the logger master switch 31 | const USE_LOGGING = Config.reduxLogging 32 | // silence these saga-based messages 33 | // create the logger 34 | const logger = createLogger({ 35 | predicate: (getState, { type }) => USE_LOGGING && R.not(R.contains(type, SAGA_LOGGING_BLACKLIST)) 36 | }) 37 | middleware.push(logger) 38 | } 39 | 40 | /* ------------- Assemble Middleware ------------- */ 41 | 42 | enhancers.push(applyMiddleware(...middleware)) 43 | 44 | /* ------------- AutoRehydrate Enhancer ------------- */ 45 | 46 | // add the autoRehydrate enhancer 47 | if (ReduxPersist.active) { 48 | enhancers.push(autoRehydrate()) 49 | } 50 | 51 | // in dev mode, we'll create the store through Reactotron 52 | const createAppropriateStore = __DEV__ ? console.tron.createStore : createStore 53 | const store = createAppropriateStore(rootReducer, compose(...enhancers)) 54 | 55 | // configure persistStore and check reducer version number 56 | if (ReduxPersist.active) { 57 | RehydrationServices.updateReducers(store) 58 | } 59 | 60 | // kick off root saga 61 | // sagaMiddleware.run(rootSaga) 62 | 63 | return store 64 | } 65 | -------------------------------------------------------------------------------- /demo/src/redux/DemoRedux.js: -------------------------------------------------------------------------------- 1 | import { createReducer, createActions } from 'reduxsauce' 2 | import Immutable from 'seamless-immutable' 3 | 4 | /* ------------- Types and Action Creators ------------- */ 5 | 6 | const { Types, Creators } = createActions({ 7 | nop: [ '' ], 8 | nopFunc: null, 9 | asyncFunc: () => { 10 | return (dispatch, getState) => { 11 | 12 | } 13 | } 14 | }) 15 | 16 | export const DemoTypes = Types 17 | export default Creators 18 | 19 | /* ------------- Initial State ------------- */ 20 | 21 | export const INITIAL_STATE = Immutable({ 22 | error: null, 23 | fetching: false, 24 | }) 25 | 26 | /* ------------- Reducers ------------- */ 27 | 28 | export const request = (state, { username, password }) => { 29 | return state.merge({ fetching: true, error: false }) 30 | } 31 | 32 | export const success = (state, { msg }) => { 33 | return state.merge({ fetching: false, error: false, msg }) 34 | } 35 | 36 | export const failure = (state, { error }) => { 37 | return state.merge({ fetching: false, error: true }) 38 | } 39 | 40 | // we've logged out 41 | export const logout = (state) => INITIAL_STATE 42 | 43 | /* ------------- Hookup Reducers To Types ------------- */ 44 | 45 | export const reducer = createReducer(INITIAL_STATE, { 46 | [Types.NOP]: request, 47 | [Types.NOP_FUNC]: request, 48 | }) 49 | 50 | /* ------------- Selectors ------------- */ 51 | -------------------------------------------------------------------------------- /demo/src/redux/GroupInviteRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createReducer, createActions } from 'reduxsauce' 4 | import Immutable from 'seamless-immutable' 5 | import WebIM from '@/config/WebIM' 6 | import CommonActions from '@/redux/CommonRedux' 7 | import GroupActions from '@/redux/GroupRedux' 8 | /* ------------- Types and Action Creators ------------- */ 9 | 10 | const { Types, Creators } = createActions({ 11 | addGroupRequest: [ 'msg' ], 12 | removeGroupRequest: [ 'gid', 'invitee' ], 13 | // ----------------async------------------ 14 | agreeInviteIntoGroup: (gid, options) => { 15 | return (dispatch, getState) => { 16 | //dispatch(Creators.removeGroupRequest(gid, options.invitee)) 17 | dispatch(CommonActions.setShowGroupInviteModal(false)) 18 | //dispatch(GroupActions.getGroups()) 19 | WebIM.conn.agreeInviteIntoGroup(options) 20 | } 21 | }, 22 | rejectInviteIntoGroup: (gid, options) => { 23 | return (dispatch, getState) => { 24 | dispatch(CommonActions.setShowGroupInviteModal(false)) 25 | //dispatch(GroupActions.getGroups()) 26 | //dispatch(Creators.removeGroupRequest(gid, options.invitee)) 27 | 28 | WebIM.conn.rejectInviteIntoGroup(options) 29 | } 30 | } 31 | }) 32 | 33 | export const GroupInviteTypes = Types 34 | export default Creators 35 | 36 | /* ------------- Initial State ------------- */ 37 | 38 | export const INITIAL_STATE = Immutable({ 39 | byGid: {} 40 | }) 41 | 42 | /* ------------- Reducers ------------- */ 43 | 44 | export const addGroupRequest = (state, { msg }) => { 45 | return state.setIn([ 'byGid', msg.gid, msg.from ], msg) 46 | } 47 | 48 | export const removeGroupRequest = (state, { gid, applicant }) => { 49 | const byGid = state.getIn([ 'byGid', gid ], Immutable({})).without(applicant) 50 | return state.setIn([ 'byGid', gid ], byGid) 51 | } 52 | 53 | /* ------------- Hookup Reducers To Types ------------- */ 54 | 55 | export const reducer = createReducer(INITIAL_STATE, { 56 | [Types.ADD_GROUP_REQUEST]: addGroupRequest, 57 | [Types.REMOVE_GROUP_REQUEST]: removeGroupRequest 58 | }) 59 | 60 | /* ------------- Selectors ------------- */ 61 | -------------------------------------------------------------------------------- /demo/src/redux/GroupRequestRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createReducer, createActions } from 'reduxsauce' 4 | import Immutable from 'seamless-immutable' 5 | import WebIM from '@/config/WebIM' 6 | import { store } from '@/redux' 7 | import CommonActions from '@/redux/CommonRedux' 8 | /* ------------- Types and Action Creators ------------- */ 9 | 10 | const { Types, Creators } = createActions({ 11 | addGroupRequest: [ 'msg' ], 12 | removeGroupRequest: [ 'gid', 'applicant' ], 13 | // ----------------async------------------ 14 | agreeJoinGroup: (gid, options) => { 15 | return (dispatch, getState) => { 16 | dispatch(Creators.removeGroupRequest(gid, options.applicant)) 17 | 18 | WebIM.conn.agreeJoinGroup(options) 19 | 20 | if (Object.keys(getState().entities.groupRequest.byGid).length <= 1 && 21 | Object.keys(getState().entities.groupRequest.byGid[gid]).length == 0) { 22 | store.dispatch(CommonActions.setShowGroupRequestModal(false)) 23 | } 24 | } 25 | }, 26 | rejectJoinGroup: (gid, options) => { 27 | return (dispatch, getState) => { 28 | dispatch(Creators.removeGroupRequest(gid, options.applicant), getState()) 29 | 30 | WebIM.conn.rejectJoinGroup(options) 31 | 32 | if (Object.keys(getState().entities.groupRequest.byGid).length <= 1 && 33 | Object.keys(getState().entities.groupRequest.byGid[gid]).length == 0) { 34 | store.dispatch(CommonActions.setShowGroupRequestModal(false)) 35 | } 36 | } 37 | } 38 | }) 39 | 40 | export const GroupRequestTypes = Types 41 | export default Creators 42 | 43 | /* ------------- Initial State ------------- */ 44 | 45 | export const INITIAL_STATE = Immutable({ 46 | byGid: {} 47 | }) 48 | 49 | /* ------------- Reducers ------------- */ 50 | 51 | export const addGroupRequest = (state, { msg }) => { 52 | return state.setIn([ 'byGid', msg.gid, msg.from ], msg) 53 | } 54 | 55 | export const removeGroupRequest = (state, { gid, applicant }) => { 56 | const byGid = state.getIn([ 'byGid', gid ], Immutable({})).without(applicant) 57 | return state.setIn([ 'byGid', gid ], byGid) 58 | } 59 | 60 | /* ------------- Hookup Reducers To Types ------------- */ 61 | 62 | export const reducer = createReducer(INITIAL_STATE, { 63 | [Types.ADD_GROUP_REQUEST]: addGroupRequest, 64 | [Types.REMOVE_GROUP_REQUEST]: removeGroupRequest 65 | }) 66 | 67 | /* ------------- Selectors ------------- */ 68 | -------------------------------------------------------------------------------- /demo/src/redux/IndexRedux.js: -------------------------------------------------------------------------------- 1 | export const MATCH_MEDIA = 'INDEX::MATCH_MEDIA' 2 | export const USER_LOGOUT = 'INDEX::LOGOUT' 3 | 4 | const defaultState = {} 5 | 6 | export function breakpointReducer(state = defaultState, action) { 7 | switch (action.type) { 8 | case MATCH_MEDIA: 9 | return { 10 | ...state, 11 | [action.k]: action.v.matches 12 | } 13 | default: 14 | return state 15 | } 16 | } 17 | 18 | export function combinedReducer(state = {}, action) { 19 | switch (action.type) { 20 | case USER_LOGOUT: 21 | return {} 22 | default: 23 | return state 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/src/redux/ResetPasswordRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { createReducer, createActions } from "reduxsauce"; 3 | import Immutable from "seamless-immutable"; 4 | import { message } from "antd"; 5 | import axios from "axios"; 6 | /* ------------- Types and Action Creators ------------- */ 7 | const domain = WebIM.config.restServer 8 | const { Types, Creators } = createActions({ 9 | setImageVerifyUrl: ["url", "imageId"], 10 | // ------------- async ----------------- 11 | // 获取图片验证码 12 | getImageVerification: () => { 13 | return (dispatch, getState) => { 14 | axios 15 | .get(domain + "/inside/app/image") 16 | .then(function (response) { 17 | // 处理成功情况 18 | const url = 19 | domain + "/inside/app/image/" + response.data.data.image_id; 20 | dispatch( 21 | Creators.setImageVerifyUrl(url, response.data.data.image_id) 22 | ); 23 | }) 24 | .catch(() => { 25 | message.error("获取图片验证码失败,请刷新重试!"); 26 | }); 27 | }; 28 | } 29 | }); 30 | 31 | export const ResetpasswordTypes = Types; 32 | 33 | export default Creators; 34 | 35 | /* ------------- Initial State ------------- */ 36 | 37 | export const INITIAL_STATE = Immutable({ 38 | imageVerifyUrl: "", 39 | imageId: "", 40 | isSuccess: false 41 | }); 42 | 43 | export const resetpassword = (state = INITIAL_STATE) => { 44 | return state; 45 | }; 46 | 47 | export const setImageVerifyUrl = (state = INITIAL_STATE, { url, imageId }) => { 48 | return Immutable.merge(state, { imageVerifyUrl: url, imageId }); 49 | }; 50 | /* ------------- Hookup Reducers To Types ------------- */ 51 | 52 | export const reducer = createReducer(INITIAL_STATE, { 53 | [Types.SET_IMAGE_VERIFY_URL]: setImageVerifyUrl 54 | }); 55 | 56 | /* ------------- Selectors ------------- */ 57 | -------------------------------------------------------------------------------- /demo/src/redux/ServerRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createReducer, createActions } from 'reduxsauce' 4 | import Immutable from 'seamless-immutable' 5 | import { history } from '@/utils' 6 | 7 | 8 | /* ------------- Types and Action Creators ------------- */ 9 | 10 | const { Types, Creators } = createActions({ 11 | jumpServer: null, 12 | // ------------- async ----------------- 13 | }) 14 | 15 | export const LoginTypes = Types 16 | export default Creators 17 | 18 | /* ------------- Initial State ------------- */ 19 | 20 | export const INITIAL_STATE = Immutable({ 21 | 22 | }) 23 | 24 | /* ------------- Reducers ------------- */ 25 | 26 | export const jumpServer = (state) => { 27 | history.push('/server') 28 | return state 29 | } 30 | 31 | 32 | /* ------------- Hookup Reducers To Types ------------- */ 33 | 34 | export const reducer = createReducer(INITIAL_STATE, { 35 | [Types.JUMP_SERVER]: jumpServer, 36 | }) 37 | 38 | /* ------------- Selectors ------------- */ 39 | 40 | -------------------------------------------------------------------------------- /demo/src/redux/StartupRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createActions } from 'reduxsauce' 4 | 5 | /* ------------- Types and Action Creators ------------- */ 6 | 7 | const { Types, Creators } = createActions({ 8 | startup: null 9 | }) 10 | 11 | export const StartupTypes = Types 12 | export default Creators 13 | -------------------------------------------------------------------------------- /demo/src/redux/StrangerRedux.js: -------------------------------------------------------------------------------- 1 | import { createReducer, createActions } from 'reduxsauce' 2 | import Immutable from 'seamless-immutable' 3 | import _ from 'lodash' 4 | import { parseFromServer } from '@/redux/MessageRedux' 5 | 6 | /* ------------- Types and Action Creators ------------- */ 7 | 8 | const { Types, Creators } = createActions({ 9 | updateStrangerMessage: [ 'stranger', 'message', 'bodyType' ], 10 | deleteStranger: [ 'stranger' ], 11 | topStranger: [ 'name' ], 12 | // ---------------async------------------ 13 | }) 14 | 15 | export const StrangerTypes = Types 16 | export default Creators 17 | 18 | /* ------------- Initial State ------------- */ 19 | 20 | export const INITIAL_STATE = Immutable({ 21 | byId: {}, 22 | name: '', 23 | stranger: [] 24 | }) 25 | 26 | export const topStranger = (state, { name }) => { 27 | let stranger = state.getIn([ 'stranger' ], Immutable([])).asMutable() 28 | if (stranger[0] === name) return state // if already top, return directly 29 | stranger = _.without(stranger, name) 30 | stranger.unshift(name) 31 | return state.merge({ stranger }) 32 | } 33 | 34 | /* ------------- Reducers ------------- */ 35 | 36 | export const updateStrangerMessage = (state, { stranger, message, bodyType = 'txt' }) => { 37 | // TODO: when receiving friend request, it should move contact with his messages to roster 38 | !message.status && (message = parseFromServer(message, bodyType)) 39 | const { username = '' } = state.user || {} 40 | const { id, to, status } = message 41 | let { type } = message 42 | 43 | // source of message, default as current user when it's empty 44 | const from = message.from || username 45 | // true when the message was sent by current user, otherwise is false 46 | const bySelf = from == username 47 | // root id, is id of receiver when sent by current user, otherwise is id of sender 48 | const chatId = bySelf || type !== 'chat' ? to : from 49 | 50 | state = state.setIn([ 'byId', stranger, id ], { 51 | ...message, 52 | bySelf, 53 | time: +new Date(), 54 | status: status 55 | }) 56 | return state 57 | } 58 | 59 | export const deleteStranger = (state, { stranger }) => { 60 | let byId = state.byId.asMutable() 61 | delete byId[stranger] 62 | return state.merge({ byId: byId }) 63 | } 64 | 65 | /* ------------- Hookup Reducers To Types ------------- */ 66 | 67 | export const reducer = createReducer(INITIAL_STATE, { 68 | [Types.UPDATE_STRANGER_MESSAGE]: updateStrangerMessage, 69 | [Types.DELETE_STRANGER]: deleteStranger, 70 | [Types.TOP_STRANGER]: topStranger 71 | }) 72 | 73 | /* ------------- Selectors ------------- */ 74 | -------------------------------------------------------------------------------- /demo/src/redux/SubscribeRedux.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { createReducer, createActions } from 'reduxsauce' 4 | import Immutable from 'seamless-immutable' 5 | import WebIM from '@/config/WebIM' 6 | 7 | /* ------------- Types and Action Creators ------------- */ 8 | 9 | const { Types, Creators } = createActions({ 10 | addSubscribe: [ 'msg' ], 11 | removeSubscribe: [ 'name' ], 12 | // ----------------async------------------ 13 | 14 | // accept add friend action 15 | acceptSubscribe: name => { 16 | return (dispatch, getState) => { 17 | dispatch(Creators.removeSubscribe(name)) 18 | 19 | WebIM.conn.acceptContactInvite(name) 20 | } 21 | }, 22 | 23 | //refuse add friend action 24 | declineSubscribe: name => { 25 | return (dispatch, getState) => { 26 | dispatch(Creators.removeSubscribe(name)) 27 | 28 | WebIM.conn.declineContactInvite(name) 29 | } 30 | } 31 | }) 32 | 33 | export const SubscribeTypes = Types 34 | export default Creators 35 | 36 | /* ------------- Initial State ------------- */ 37 | 38 | export const INITIAL_STATE = Immutable({ 39 | byFrom: {} 40 | }) 41 | 42 | /* ------------- Reducers ------------- */ 43 | 44 | export const subscribe = (state, { msg }) => { 45 | return Immutable.setIn(state, [ 'byFrom', msg.from ], msg) 46 | } 47 | 48 | export const removeSubscribe = (state, { name }) => { 49 | let byFrom = Immutable.without(state.byFrom, name) 50 | return Immutable.set(state, [ 'byFrom' ], byFrom) 51 | } 52 | 53 | /* ------------- Hookup Reducers To Types ------------- */ 54 | 55 | export const reducer = createReducer(INITIAL_STATE, { 56 | [Types.ADD_SUBSCRIBE]: subscribe, 57 | [Types.REMOVE_SUBSCRIBE]: removeSubscribe 58 | }) 59 | 60 | /* ------------- Selectors ------------- */ 61 | -------------------------------------------------------------------------------- /demo/src/selectors/ChatSelector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | import _ from 'lodash' 3 | 4 | const chatType = { 5 | contact: 'chat', 6 | group: 'groupchat', 7 | chatroom: 'chatroom', 8 | stranger: 'stranger' 9 | } 10 | 11 | const getTabMessageArray = (state, props) => { 12 | const { selectTab, selectItem } = props.match.params 13 | var msgByItem = _.get(state, [ 'entities', 'message', chatType[selectTab], selectItem ]) 14 | return msgByItem 15 | } 16 | 17 | const getTabMessageArray2 = (state, props) =>{ 18 | const { selectTab, selectItem } = props.match.params 19 | var msgByMid = _.get(state, [ 'entities', 'message', 'byMid' ]) 20 | return msgByMid 21 | } 22 | 23 | 24 | const getTabMessages = createSelector( 25 | [ getTabMessageArray, getTabMessageArray2 ], 26 | (TabMessageArray, TabMessageArray2) => { 27 | return { TabMessageArray, TabMessageArray2 } 28 | } 29 | ) 30 | 31 | export default getTabMessages -------------------------------------------------------------------------------- /demo/src/selectors/ContactSelector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | import _ from 'lodash' 3 | 4 | const currentContacts = (state, { selectTab, selectItem }) => { 5 | const contactType = selectTab === 'contact' ? 'roster' : selectTab 6 | return _.get(state, [ 'entities', contactType ], []) 7 | } 8 | 9 | const getCurrentContacts = createSelector( 10 | [ currentContacts ], 11 | (contacts) => contacts 12 | ) 13 | 14 | export default getCurrentContacts 15 | -------------------------------------------------------------------------------- /demo/src/themes/css/fontello.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fontello'; 3 | src: url('../fontello/fontello.eot?38217245'); 4 | src: url('../fontello/fontello.eot?38217245#iefix') format('embedded-opentype'), 5 | url('../fontello/fontello.woff2?38217245') format('woff2'), 6 | url('../fontello/fontello.woff?38217245') format('woff'), 7 | url('../fontello/fontello.ttf?38217245') format('truetype'), 8 | url('../fontello/fontello.svg?38217245#fontello') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 14 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 15 | /* 16 | @media screen and (-webkit-min-device-pixel-ratio:0) { 17 | @font-face { 18 | font-family: 'fontello'; 19 | src: url('../font/fontello.svg?38217245#fontello') format('svg'); 20 | } 21 | } 22 | */ 23 | 24 | /* [class^="icon-"]:before, [class*=" icon-"]:before */ 25 | .fontello { 26 | font-family: "fontello"; 27 | font-style: normal; 28 | font-weight: normal; 29 | speak: none; 30 | 31 | display: inline-block; 32 | text-decoration: inherit; 33 | width: 1em; 34 | margin-right: .2em; 35 | text-align: center; 36 | /* opacity: .8; */ 37 | 38 | /* For safety - reset parent styles, that can break glyph codes*/ 39 | font-variant: normal; 40 | text-transform: none; 41 | 42 | /* fix buttons height, for twitter bootstrap */ 43 | line-height: 1em; 44 | 45 | /* Animation center compensation - margins should be symmetric */ 46 | /* remove if not needed */ 47 | margin-left: .2em; 48 | 49 | /* you can be more comfortable with increased icons size */ 50 | /* font-size: 120%; */ 51 | 52 | /* Font smoothing. That was taken from TWBS */ 53 | -webkit-font-smoothing: antialiased; 54 | -moz-osx-font-smoothing: grayscale; 55 | 56 | /* Uncomment for 3D effect */ 57 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 58 | } 59 | 60 | .icon-volume-off:before { 61 | content: '\e800'; 62 | } 63 | 64 | /* '' */ 65 | .icon-volume-up:before { 66 | content: '\e801'; 67 | } 68 | 69 | /* '' */ 70 | .icon-comment:before { 71 | content: '\e802'; 72 | } 73 | 74 | /* '' */ 75 | .icon-chat:before { 76 | content: '\e803'; 77 | } 78 | 79 | /* '' */ 80 | .icon-dot-3:before { 81 | content: '\e807'; 82 | } 83 | 84 | /* '' */ 85 | .icon-ok-circle:before { 86 | content: '\e808'; 87 | } 88 | 89 | /* '' */ 90 | .icon-users-1:before { 91 | content: '\f064'; 92 | } 93 | 94 | /* '' */ 95 | .icon-mic:before { 96 | content: '\f130'; 97 | } 98 | 99 | /* '' */ 100 | .icon-mute:before { 101 | content: '\f131'; 102 | } 103 | 104 | /* '' */ 105 | .icon-paper-plane:before { 106 | content: '\f1d8'; 107 | } 108 | 109 | /* '' */ 110 | .icon-circle-thin:before { 111 | content: '\f1db'; 112 | } 113 | 114 | /* '' */ 115 | .icon-address-book-1:before { 116 | content: '\f2b9'; 117 | } 118 | 119 | /* '' */ 120 | -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_1.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_10.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_11.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_12.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_13.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_14.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_15.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_16.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_17.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_18.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_19.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_2.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_20.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_21.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_22.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_23.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_24.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_25.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_26.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_27.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_28.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_29.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_3.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_30.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_31.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_32.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_33.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_34.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_35.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_4.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_5.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_6.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_7.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_8.png -------------------------------------------------------------------------------- /demo/src/themes/faces/ee_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/faces/ee_9.png -------------------------------------------------------------------------------- /demo/src/themes/font/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "webim"; 3 | src: url('iconfont.eot?t=1477721984098'); /* IE9*/ 4 | src: url('iconfont.eot?t=1477721984098#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('iconfont.woff?t=1477721984098') format('woff'), /* chrome, firefox */ 6 | url('iconfont.ttf?t=1477721984098') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1477721984098#webim') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .webim { 11 | font-family:"webim" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -webkit-text-stroke-width: 0.2px; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | .webim-K:before { content: "\4b"; } 20 | 21 | .webim-D:before { content: "\44"; } 22 | 23 | .webim-z:before { content: "\7a"; } 24 | 25 | .webim-i:before { content: "\69"; } 26 | 27 | .webim-A:before { content: "\41"; } 28 | 29 | .webim-J:before { content: "\4a"; } 30 | 31 | .webim-T:before { content: "\54"; } 32 | 33 | .webim-d:before { content: "\64"; } 34 | 35 | .webim-R:before { content: "\52"; } 36 | 37 | .webim-C:before { content: "\43"; } 38 | 39 | .webim-l:before { content: "\6c"; } 40 | 41 | .webim-L:before { content: "\4c"; } 42 | 43 | .webim-a:before { content: "\61"; } 44 | 45 | .webim-U:before { content: "\55"; } 46 | 47 | .webim-e:before { content: "\65"; } 48 | 49 | .webim-F:before { content: "\46"; } 50 | 51 | .webim-H:before { content: "\48"; } 52 | 53 | .webim-I:before { content: "\49"; } 54 | 55 | .webim-V:before { content: "\56"; } 56 | 57 | .webim-j:before { content: "\6a"; } 58 | 59 | .webim-S:before { content: "\53"; } 60 | 61 | .webim-b:before { content: "\62"; } 62 | 63 | .webim-c:before { content: "\63"; } 64 | 65 | .webim-O:before { content: "\4f"; } 66 | 67 | .webim-k:before { content: "\6b"; } 68 | 69 | .webim-M:before { content: "\4d"; } 70 | 71 | .webim-P:before { content: "\50"; } 72 | 73 | .webim-W:before { content: "\57"; } 74 | 75 | .webim-E:before { content: "\45"; } 76 | 77 | .webim-X:before { content: "\58"; } 78 | 79 | .webim-G:before { content: "\47"; } 80 | 81 | .webim-Q:before { content: "\51"; } 82 | 83 | .webim-B:before { content: "\42"; } 84 | 85 | .webim-h:before { content: "\68"; } 86 | 87 | .webim-f:before { content: "\66"; } 88 | 89 | .webim-m:before { content: "\6d"; } 90 | 91 | .webim-L1:before { content: "\4e"; } 92 | 93 | .webim-Z:before { content: "\5a"; } 94 | 95 | .webim-n:before { content: "\6e"; } 96 | 97 | -------------------------------------------------------------------------------- /demo/src/themes/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/font/iconfont.eot -------------------------------------------------------------------------------- /demo/src/themes/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/font/iconfont.ttf -------------------------------------------------------------------------------- /demo/src/themes/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/font/iconfont.woff -------------------------------------------------------------------------------- /demo/src/themes/fontello/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/fontello/fontello.eot -------------------------------------------------------------------------------- /demo/src/themes/fontello/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/fontello/fontello.ttf -------------------------------------------------------------------------------- /demo/src/themes/fontello/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/fontello/fontello.woff -------------------------------------------------------------------------------- /demo/src/themes/fontello/fontello.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/fontello/fontello.woff2 -------------------------------------------------------------------------------- /demo/src/themes/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1502424015968'); /* IE9*/ 4 | src: url('iconfont.eot?t=1502424015968#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('iconfont.woff?t=1502424015968') format('woff'), /* chrome, firefox */ 6 | url('iconfont.ttf?t=1502424015968') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1502424015968#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-ic-schedule-px:before { content: "\e6a2"; } 19 | 20 | .icon-alarm:before { content: "\e6b6"; } 21 | 22 | .icon-camera-video:before { content: "\e6b7"; } 23 | 24 | .icon-bubble:before { content: "\e6b8"; } 25 | 26 | .icon-arrow-down-circle:before { content: "\e6b9"; } 27 | 28 | .icon-arrow-right:before { content: "\e6ba"; } 29 | 30 | .icon-arrow-left:before { content: "\e6bb"; } 31 | 32 | .icon-camera:before { content: "\e6bc"; } 33 | 34 | .icon-cross:before { content: "\e6bd"; } 35 | 36 | .icon-download:before { content: "\e6be"; } 37 | 38 | .icon-cog:before { content: "\e6bf"; } 39 | 40 | .icon-exit:before { content: "\e6c0"; } 41 | 42 | .icon-checkmark-circle:before { content: "\e6c1"; } 43 | 44 | .icon-layers:before { content: "\e6c2"; } 45 | 46 | .icon-circle-minus:before { content: "\e6c3"; } 47 | 48 | .icon-file-empty:before { content: "\e6c4"; } 49 | 50 | .icon-lock:before { content: "\e6c5"; } 51 | 52 | .icon-magnifier:before { content: "\e6c6"; } 53 | 54 | .icon-map-marker:before { content: "\e6c7"; } 55 | 56 | .icon-mic:before { content: "\e6c8"; } 57 | 58 | .icon-menu:before { content: "\e6c9"; } 59 | 60 | .icon-pencil:before { content: "\e6ca"; } 61 | 62 | .icon-mustache:before { content: "\e6cb"; } 63 | 64 | .icon-picture:before { content: "\e6cc"; } 65 | 66 | .icon-phone-handset:before { content: "\e6cd"; } 67 | 68 | .icon-smile:before { content: "\e6ce"; } 69 | 70 | .icon-plus-circle:before { content: "\e6cf"; } 71 | 72 | .icon-trash:before { content: "\e6d0"; } 73 | 74 | .icon-user:before { content: "\e6d1"; } 75 | 76 | .icon-users:before { content: "\e6d2"; } 77 | 78 | .icon-hyphenate:before { content: "\e6d3"; } 79 | 80 | -------------------------------------------------------------------------------- /demo/src/themes/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/iconfont/iconfont.eot -------------------------------------------------------------------------------- /demo/src/themes/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /demo/src/themes/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/iconfont/iconfont.woff -------------------------------------------------------------------------------- /demo/src/themes/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/1.jpg -------------------------------------------------------------------------------- /demo/src/themes/img/acceptCall@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/acceptCall@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/avatar-big@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/avatar-big@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/avatar@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/avatar@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/avtool.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/src/themes/img/camera-close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/camera-close@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/camera@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/default-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/default-img.png -------------------------------------------------------------------------------- /demo/src/themes/img/hangupCall@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/hangupCall@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/invite_member@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/invite_member@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/microphone-mute@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/microphone-mute@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/microphone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/microphone@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/minimodal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/minimodal@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/narrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/narrow@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/rtc-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/rtc-bg@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/talking@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/talking@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/video-bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/video-bg2.png -------------------------------------------------------------------------------- /demo/src/themes/img/video-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/video-bg@2x.png -------------------------------------------------------------------------------- /demo/src/themes/img/video-loading@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/img/video-loading@2x.png -------------------------------------------------------------------------------- /demo/src/themes/ngprogress.less: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: @primary-color; 8 | position: fixed; 9 | z-index: 1031; 10 | top: 0; 11 | left: 0; 12 | width: 100%; 13 | height: 2px; 14 | border-radius: 10px; 15 | } 16 | 17 | /* Fancy blur effect */ 18 | #nprogress .peg { 19 | display: block; 20 | position: absolute; 21 | right: 0; 22 | width: 100px; 23 | height: 100%; 24 | box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color; 25 | opacity: 1; 26 | transform: rotate(3deg) translate(0, -4px); 27 | } 28 | 29 | /* Remove these to get rid of the spinner */ 30 | #nprogress .spinner { 31 | display: block; 32 | position: fixed; 33 | z-index: 1031; 34 | top: 15px; 35 | right: 15px; 36 | } 37 | 38 | #nprogress .spinner-icon { 39 | width: 18px; 40 | height: 18px; 41 | box-sizing: border-box; 42 | border: solid 2px transparent; 43 | border-top-color: @primary-color; 44 | border-left-color: @primary-color; 45 | border-radius: 50%; 46 | animation: nprogress-spinner 400ms linear infinite; 47 | } 48 | 49 | .nprogress-custom-parent { 50 | overflow: hidden; 51 | position: relative; 52 | } 53 | 54 | .nprogress-custom-parent #nprogress .spinner, 55 | .nprogress-custom-parent #nprogress .bar { 56 | position: absolute; 57 | } 58 | 59 | @keyframes nprogress-spinner { 60 | 0% { 61 | transform: rotate(0deg); 62 | } 63 | 100% { 64 | transform: rotate(360deg); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /demo/src/themes/theme.less: -------------------------------------------------------------------------------- 1 | @import "~antd/dist/antd.less"; 2 | // 引入官方提供的 less 样式入口文件 3 | @import "./css/fontello.css"; 4 | @import "./iconfont/iconfont.css"; 5 | @import "./font/iconfont.css"; 6 | 7 | // New Icon font from cyn 8 | @import "./webim/iconfont.css"; 9 | @import "./webim2/iconfont.css"; 10 | 11 | // @import "./theme.less"; // 引入官方提供的 less 样式入口文件 12 | 13 | // @btn-primary-bg: 000; 14 | // @btn-default-bg: red; 15 | 16 | // // @primary-color: #1da57a; 17 | // @primary-color: yellow; 18 | 19 | @green-1: #00ba6e; 20 | // -------- Colors ----------- 21 | @primary-color: @green-1; 22 | @info-color: @green-1; 23 | @success-color: @green-6; 24 | @error-color: @red-6; 25 | @highlight-color: @red-6; 26 | @warning-color: @yellow-6; 27 | @normal-color: #d9d9d9; 28 | @border-radius-base: 2px; 29 | 30 | // @btn-primary-color : #00ba6e; 31 | // @btn-primary-bg : #00ba6e; 32 | // @btn-default-color : #00ba6e; 33 | // @btn-default-bg : #fff; 34 | // @btn-default-border : #00ba6e; 35 | 36 | @import './ngprogress.less'; 37 | @import '../layout/style/index.less'; 38 | @import '../components/header/style/index.less'; 39 | @import '../components/contact/style/index.less'; 40 | @import '../components/list/style/index.less'; 41 | @import '../components/chat/style/index.less'; 42 | // @import './components/contact'; 43 | 44 | // default theme 45 | body { 46 | .headerBg { 47 | background-color: #000; 48 | } 49 | 50 | // overflow: hidden; 51 | // height: 100%; 52 | // 禁止移动端下拉等touch操作的的缓动,是的整体和内部滚动区域冲突导致的滚动不明显 53 | position: fixed; 54 | top: 0; 55 | bottom: 0; 56 | left: 0; 57 | right: 0; 58 | } 59 | 60 | .hide { 61 | display: none !important; 62 | } 63 | 64 | input[type=file] { 65 | position: absolute; 66 | top: 0; 67 | right: 0; 68 | min-width: 100%; 69 | min-height: 100%; 70 | filter: alpha(opacity=0); 71 | opacity: 0; 72 | cursor: inherit; 73 | display: block; 74 | } 75 | -------------------------------------------------------------------------------- /demo/src/themes/webim/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1580971995915'); /* IE9 */ 3 | src: url('iconfont.eot?t=1580971995915#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAbMAAsAAAAADaAAAAZ/AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCEFgqMZIo5ATYCJAMsCxgABCAFhG0HgRwblQtRVHBiyX5q8sSWto1t6j6cWtagS7spfgwAACTwAAB2O5QheL7fr537RD+iEql4KHjS1nZJdFL16UxniPZmNqL1/+9c5oA4dnQkB0QKu/anl6P0pRk5QOdIWFQTcvPABPj76RXIoVJE6k/4VY5QEb4sfqlwkx6AbYfqtJmknI1QSrsVtDmo9QVHITj4BSD/N+bqNkT1t9/Wi8lhdjtUpJE0pBHNJHnII1ISj1Ijh+yK2Bx11RPHQ0Ayrp1hbHR6GdkZSCVqqRWqSsheCmV6rEQIcC3bJpEhFiF+pZ7hWn6//KvE7KDYBOhEe3MjCur/mmtFOtTBWiJwCEcBayfQYBXCQE5aWROZUuWqJKntamanEO0CxX3Nla2yX8Lb+ltU15Ttd8thfcREO7bqFv0HD6EZluMFUSIUJI1KKl2vmZ1Q5ihCo2whDMouwqLsIRzKPsKjHCICyhEiogREQquPELSiKigAmVfsh5iEVE4NOwaZHiHqEaWK1ZQaC+W+ysnc0eE7nctMzLls1ZDxTkX0sFOTuVg04TSvy2L1Hzxpmr/ihg9j3zEjyvLj3ghwYRlcIA9TqbIh3o9jkOjaHjFwmRDiWfjGnX1puHgZGPYV2Xhm20pVKH5O3i9bDyjkOgEh3SKAsq3CMmz/cQCy1iHagRMG1KUnbYnQzqB2MdgkB6TC+RwaJeBala9MKDM4oYQq1YgCJ+WlcEQC1HkXIQRIMUILpBAyzhInkQzDqBCTqMHtVZMKxJ5XmFNI4INcUFrAUoelEMpkIxI+WJV1KCoWWVSpJHTRYgUUCnwksGptVf8MukoeZUyWulZVKZdnjWMqlSWQtkH/gROikyPHyWMlyYs1i9GCLVAkFG48uXMtFCkDpFC0FRvhbz59WkHK7U4l5S3eTm7TEw9ZDsqEQtlS0RKpVGBXkuQoCUK8AHVRiKHqj2F3M7M3rl+8HGiQizZtaF665Fin7IiQJVT8NdRllWB85w7LRWdvtDUJOPHGbo5RE92NQh7zum/vNNJNbQJbqEy3LWFq01aqOmwrDnC5tDC333crM7tjrI+bv3kF8JscxrPMcTof2KLBtDJ/8mTP7rym7SKC37xNrPKK6K0MD7/yjDvzy2u1teMzORMOOZ6REbQkT+CzJvplPjOVG/+rZfBnjFdMj/zZva5kXTR5YjXhIJwHLtI9aBs/dc4/Wr/+OjBqDGKF8mJtnI3SJqlu1qCOPy7N0hXHJ2VY21V7wbwSfJgIxEG8bSuoSwFNTRfQPlZEDl1NExSCcwhTKVCDiFx1/V2CXeoqF0ykGRsLUHrKrVv0NRGfld18xPh2ejJKKN/+6Lh5i85RzlGdct22BZe956HHvMdobqFu3xAsKMEe8x9jjTCxsSIdeEpu/NkZM/NkASSi9OTbhjCZeorDYpjXNZiPTbVMqCs4QjqlucaoRecY55ou8IRmrGtReIhwkJ4L/A9zMGY0k/2AHey9/IWhn0/ujtdcZhSTJltbiuJExpOL0p1kdolfDfsuL7If6TI4DC0+sI1ZEVdmWD6xw06OXmvtL3c+f52YGZkbXhw57PBVb6NwG5O+m9Ly3dkstnlb9tsH3tP9f1Q/+xoB16CNrq+/DMBU45tlPo9sbK7mdTm0dJGj1b7C6rjNRttmL1VTN92qpfAyqZx9yhtF2p4cvUBHMtQV9YEkugkCPVT/crEJ4yv1B01MecVY/e4AOsgT9X/TkXpT/P/n+F9+u5ejkRzb2PT1/rKuH/yI18mXe12+jX94z6oEUPRdloxsZ0IZEkO0ChXP9MkuWTIUSLgffJPjur3C+/eNJvxt+PfloHhjoAWzXKO3Cla2Bk6wCcmKsZ2zHpyJJCOw7EwB0ekKlHaPoHV65hq9F7D6fYHTGTNBchIr7TObj1FnxEESoODtA0xl1iQkQjiT0oug7tQlXpRJz1XgOk3FpXyxmjsGTeAQG+i7alkIgglnDTyqnAzqdYY1zqogi3xFCK2zUCBtF8jLrIGcdnFAIgAFtu2A81MypokMLzhr2l8EVDvqJLyn0YB/FeB0VHOsJK84gjtGmqMaPZSKul2qMqFRAtOaHNOAjWoU1HEWg2ntS1UBmZBXmZGh6VTQOiNjSX57/8b9PQFI0G/KMSVajFhx4iVIlCRZKqo1mQM0O/SKxCGgC6Z11A7h2KQKsE1GiGlSUWyrVM6JVW7QNK8yDs41DaQa8HKgK4shxD5J3UQI') format('woff2'), 5 | url('iconfont.woff?t=1580971995915') format('woff'), 6 | url('iconfont.ttf?t=1580971995915') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1580971995915#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-screen-share:before { 19 | content: "\e93d"; 20 | } 21 | 22 | .icon-stop-screen-share:before { 23 | content: "\e943"; 24 | } 25 | 26 | .icon-video_off:before { 27 | content: "\e666"; 28 | } 29 | 30 | .icon-add:before { 31 | content: "\e677"; 32 | } 33 | 34 | .icon-mic_off:before { 35 | content: "\e67a"; 36 | } 37 | 38 | .icon-mic_on:before { 39 | content: "\e67b"; 40 | } 41 | 42 | .icon-more:before { 43 | content: "\e67c"; 44 | } 45 | 46 | .icon-speaker_on:before { 47 | content: "\e67e"; 48 | } 49 | 50 | .icon-speaker_off:before { 51 | content: "\e67f"; 52 | } 53 | 54 | .icon-video_on:before { 55 | content: "\e680"; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /demo/src/themes/webim/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/webim/iconfont.eot -------------------------------------------------------------------------------- /demo/src/themes/webim/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/webim/iconfont.ttf -------------------------------------------------------------------------------- /demo/src/themes/webim/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/webim/iconfont.woff -------------------------------------------------------------------------------- /demo/src/themes/webim2/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "webim"; 2 | src: url('iconfont.eot?t=1532400519021'); /* IE9*/ 3 | src: url('iconfont.eot?t=1532400519021#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAb0AAsAAAAACiwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAAQwAAAFZW6WJqY21hcAAAAYAAAABnAAABqAqmCidnbHlmAAAB6AAAAuQAAAO0b8pCv2hlYWQAAATMAAAALAAAADYSF6zwaGhlYQAABPgAAAAcAAAAJAfeA4dobXR4AAAFFAAAAA4AAAAYGAAAAGxvY2EAAAUkAAAADgAAAA4DcgIabWF4cAAABTQAAAAfAAAAIAEVAGluYW1lAAAFVAAAAVEAAAJtTbo+h3Bvc3QAAAaoAAAATAAAAGQbe1CUeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGBwYShgqmBv+NzDEMDcwNACFGUFyAMnVCwMAeJzFkFEOgCAMQzsYfhhPYjyK3355DFBuj+uyPw9Ak9eEhg1SAAVANg5DAbkhoC5LxfOM1XPF7nfU8z6GeXUXThqcSNwoC6ZIs6Qy5+m/NvczTvxWDdhWC9j9E7DXN0hGD8oHcgAL2AB4nHWSS2jUQBjH55vszCTZNo9mkqxiU9OtydbHVvaR2NaufekWBXfBKr7AUvDioYKPgyDYi0UQLx4qXrQovSgiXgsV76IIFS9ehOqlIh4UPKjRSVF7sTkk8/r+5PvNDxEkHmkS30IWclE72orGEIJoD44c13aYBj1QhAGoxMx2w7xbrQQW5DVoB+6U/VIE5TCoRKUOKHvg2q6Tntcg7xchrNYAPwFyPaMYABLRyQyTb5w58+3ZMreTlSmdcx0mNCu5cmCcqK1sarm9gM8zwuVJdTNP7nohxgW4RqWjkFHUL5QoOHMYVLZPioYrI7/eP9N58hEc4Pon3TJg7zjVsuQshN7yBdmkbNLkUPA+egXRHqz2OI4/oJyYmH4nTX/e5KWqKVqoBHnfDEIKn5NXsqrKsNOHkqwocvISDAO/ThYVQ0kWDQNGxABGDMBp3GrmsMhkqA2dFLmhLRCUIsmtDAhkoQbMssslh1OW7xSM+qEqVitR7LgejuJ0P4UUBjXoAMEqTgFy5tbSScmxRZ0rijtTnnjiOewu+qWnb8Sn2I8v9xFZJrPpq29mnqjMkODI7TvDsTXYXTsoXaSEsxPSwZoY09GwG1Rd7dk2mFIeKpyC3PObvWO4GzreyMqn3rok1XuTLV3iDGR1tav18VVZAWwcntnfVo2D7SAdqy9clNsoPcnqxyXpeP2rY492n96VIho8t2OI6Fky7G/5x+QSXkIEeaiasg7z1TIJ46gIOoSBBrwDGPVgD7hODSCgeioTE+T6xUosejcrAQ7ezc7ibY2GkEFpNNP8ZpMRmzUbCifJQksu+2LaygHkrOkXzoaW1lb88OeDRz9eNtJ92mwSyGqs0ZQtykQhtWE+I8n3wDG/m859I0usLD7UYiGU+eN+16r7Iepb33xxVf6a9bG75njsr14c/Z/rS8LOZO6v6dJGxefJnBBbmAkTXgHkdf1+q3P4o7a2qc0SBSteCKndjvUb1EabynicY2BkYGAAYim9kz3x/DZfGbhZGEDges2WdmSahYHpKZDiYGAC8QASowlfeJxjYGRgYG7438AQw8IAAkCSkQEVsAEARwwCb3icY2FgYGDBggEBaAAZAAAAAAAAAHIAnAEmAX4B2gAAeJxjYGRgYGBjiGVgZQABJiDmAkIGhv9gPgMAEnQBfwB4nGWPO27CQBRFr/nkY6QIBSVdpCmiFElkPgoNTQok6CnowYzByPZYw4DEArKeLCEryBKSHWQPuZjnBmzN83n3fXwHwC1+4eH43PEc2cMVsyNXcIkH4Sr1R+Ea+VW4jgbehC+ovwv7eMFYuIEWLDd4tWtmz/gQ9tDEp3AFN/gSrlL/Fq6Rf4TruMef8AWaXlXYx9RrCTfw5MX+0OqZ0ws136s4NFlkMufH6UKnZtWf6OU2mdkyLb9TbTexyVQ36JTSWGfalns2u2XPuUhF1qRqxIU6SYzKrVnr0AUr5/JBux2JHoQmpa0hr60xg2NcQGGOPWOMEAYZoiI69sVIWdeMBiv0MSEvsUXCWXtWPc2nZIsN9cM+hS4CdM66xuSs6Dz1s8GOf+tRdfSkeCxnUtJIHGo6ScgKeVFbUwmpB9x8mMoxQJtvdNIfFDdN/wG/bWyIAAAAeJxjYGKAAC4G7ICNkYmRmZGFkZWRjZGdgSs/LU03OTE3tSiRJSCntJgPxM/NTC7KL8jIz0vlDM4oLdFNyS/P48zPg6pjYAAAAYESbw==') format('woff'), 5 | url('iconfont.ttf?t=1532400519021') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 6 | url('iconfont.svg?t=1532400519021#webim') format('svg'); /* iOS 4.1- */ 7 | } 8 | 9 | .webim { 10 | font-family:"webim" !important; 11 | font-size:16px; 12 | font-style:normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | .icon-s_off_sound:before { content: "\75"; } 18 | 19 | .icon-s_dot:before { content: "\76"; } 20 | 21 | .icon-s_off_the_microphone:before { content: "\77"; } 22 | 23 | .icon-s_off_camera:before { content: "\76"; } 24 | 25 | .icon-s_off_camera.camera:before { content: "\78"; } 26 | 27 | .icon-s_sound:before { content: "\41"; } 28 | 29 | -------------------------------------------------------------------------------- /demo/src/themes/webim2/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/webim2/iconfont.eot -------------------------------------------------------------------------------- /demo/src/themes/webim2/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/webim2/iconfont.ttf -------------------------------------------------------------------------------- /demo/src/themes/webim2/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/src/themes/webim2/iconfont.woff -------------------------------------------------------------------------------- /demo/src/utils/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory, createHashHistory } from 'history' 2 | // console.log(location.pathname) 3 | // export default createBrowserHistory({ 4 | // basename: "/build/" 5 | // }) 6 | 7 | export default createHashHistory() 8 | -------------------------------------------------------------------------------- /demo/src/utils/loglevel.js: -------------------------------------------------------------------------------- 1 | import * as loglevel from 'loglevel' 2 | import prefix from 'loglevel-plugin-prefix' 3 | import config from 'WebIMConfig' 4 | 5 | loglevel.setLevel(config.loglevel) 6 | prefix.apply(loglevel, { template: '[%t] %l (%n) logger: ' }) 7 | 8 | export default loglevel 9 | -------------------------------------------------------------------------------- /demo/src/utils/overlay.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | export default function overlay({ component, ...rest }) { 5 | const dom = document.createElement('div') 6 | document.body.appendChild(dom) 7 | 8 | function close() { 9 | const isUnmount = ReactDOM.unmountComponentAtNode(dom) 10 | if (isUnmount && dom.parentNode) { 11 | dom.parentNode.removeChild(dom) 12 | } 13 | } 14 | 15 | const C = component || null 16 | 17 | rest.close = close 18 | 19 | return { 20 | show: () => { 21 | ReactDOM.render(, dom) 22 | }, 23 | close 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demo/storybook-static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easemob/easemob-demo-react/434bab461d4b401fbe73d4ad70fea8f6359d8f9e/demo/storybook-static/favicon.ico -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1 3 | } 4 | -------------------------------------------------------------------------------- /simpleDemo/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDazCCAlOgAwIBAgIURGm7UWuPPv89wC/OTNHcelbtGzQwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTA2MjkwMzU1MzJaFw0yOTA2 5 | MjYwMzU1MzJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB 7 | AQUAA4IBDwAwggEKAoIBAQCSttBv1xiRD4/mps+QH5SNFYmsE7vgzMsTklb7ylXH 8 | cTWZC4Uzv2du/poM3UwJX9QrPHUkzSlXYK8RUq+KsgYuhhNeAG31+TmjOhEdpG/r 9 | rUtYcrdMROvBiH2QRruNqHIv48DEMAMX9M9CS6NBmUT15ot62TE+A/bAFpbw1Hdx 10 | 0vcAYdFWCJjNuqekT1/shhRBUbiUiA348OV4ws1sDDvM08PVMzZDok2FUImr/s96 11 | OtXKpa7rcPVPHzr42oybDLjP1Bsp4KgZN+Muv9FX1aZudi0lauqShl/RS5V/I9oT 12 | dxTLXE9frsevIqBEeNqz4I892RRKn8ne2KwnG3rCK805AgMBAAGjUzBRMB0GA1Ud 13 | DgQWBBQDYqnah8WuLm76iGcSnRIf/CipHzAfBgNVHSMEGDAWgBQDYqnah8WuLm76 14 | iGcSnRIf/CipHzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBb 15 | 053fRAGoaTjrZjq6u8LJieVNkDj5OG/rdXekBA/F0idRY2+bkZk2kv1J9XUXGyZe 16 | I58OxDLi5Wi0hy2KT6mJyzZ9fSPQx70Zx9v3nEYOhKpVvyyxfN9OJ+tXhI/yKEzE 17 | TO6k4C2jc8+8/aq3L6f0z2aQxYTHDf9pCPqQaEso/p4AIMEqd7on/docOZIYzhmt 18 | b/EuP9rXRSvJnu7WpPBwLXQZeqjzhEhgfNtl+odKnToeGCZ+xhx8I0jbBkp7r8Do 19 | iRFbUNyHhAFdPjtvNPF06if6h9XnjUUp9jyR17cWatnDbZDrEfievfatjSFb7xYm 20 | BvsXy+oEAYPMshowd1dg 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /simpleDemo/easmeob-im/friend.js: -------------------------------------------------------------------------------- 1 | 2 | $(function () { 3 | //获取好友 4 | $('#getRoasters').click(function () { 5 | WebIM.conn.getContacts({ 6 | success: function (res) { 7 | console.log('获取好友') 8 | } 9 | }) 10 | }) 11 | //添加好友 12 | $("#addRoster").click(function () { 13 | WebIM.conn.addContact(toID,u + '加个好友呗!') 14 | $('toName').val(''); 15 | }) 16 | //删除好友 17 | $('#removeRoster').click(function () { 18 | WebIM.conn.deleteContact(toID) 19 | $('#toName').val(''); 20 | }) 21 | //获取黑名单列表 22 | $('#getBlackList').click(function () { 23 | WebIM.conn.getBlocklist(); 24 | }) 25 | //将好友加入黑名单 26 | $('#addBlackList').click(function () { 27 | WebIM.conn.addToBlackList({ 28 | name: [toID], 29 | success: function (res) { 30 | console.log('加入黑名单成功', res); 31 | }, 32 | error: function (err) { 33 | console.log('加入黑名单失败', err); 34 | } 35 | }); 36 | $('#toName').val(''); 37 | }) 38 | //将好友移除黑名单 39 | $('#removeFromBlackList').click(function () { 40 | WebIM.conn.removeFromBlackList({ 41 | name: [toID], 42 | success: function (res) { 43 | console.log('移除黑名单成功', res); 44 | }, 45 | error: function (err) { 46 | console.log('移除黑名单失败', err) 47 | } 48 | }); 49 | $('#toName').val(''); 50 | }) 51 | 52 | }) 53 | -------------------------------------------------------------------------------- /simpleDemo/easmeob-im/info.js: -------------------------------------------------------------------------------- 1 | 2 | $(function () { 3 | //设置/修改 用户属性 4 | $('#updateInfo').click(function () { 5 | let options = { 6 | nickname: userNickname, 7 | avatarurl: userAvatarUrl, 8 | mail: '123@qq.com', 9 | phone: '16888888888', 10 | gender: 'female', 11 | birth: '2000-01-01', 12 | sign: 'a sign', 13 | ext: JSON.stringify({ 14 | nationality: 'China', 15 | merit: 'Hello,世界!' 16 | }) 17 | } 18 | WebIM.conn.updateOwnUserInfo(options).then((res) => { 19 | console.log('修改属性成功',res) 20 | }) 21 | }) 22 | 23 | //获取用户属性 24 | $('#fetchInfo').click(function () { 25 | if (toID) { 26 | WebIM.conn.fetchUserInfoById(toID).then((res) => { 27 | console.log('获取属性成功', res) 28 | }) 29 | }else { 30 | alert('请输入要拉取信息的ID/接收方ID!') 31 | } 32 | 33 | }) 34 | }) -------------------------------------------------------------------------------- /simpleDemo/easmeob-im/login.js: -------------------------------------------------------------------------------- 1 | 2 | $(function () { 3 | //注册账号 4 | $('#register').click(function () { 5 | var options = { 6 | username: uname, 7 | password: upwd, 8 | nickname: 'nickname', 9 | appKey: WebIM.config.appkey, 10 | success: function (res) { 11 | console.log('注册成功', res) 12 | }, 13 | error: function (err) { 14 | console.log('注册失败', err) 15 | }, 16 | apiUrl: WebIM.config.apiURL 17 | }; 18 | WebIM.conn.registerUser(options); 19 | }) 20 | //登陆 21 | $('#login').click(function () { 22 | options = { 23 | apiUrl: WebIM.config.apiURL, 24 | user: uname, 25 | pwd: upwd, 26 | appKey: WebIM.config.appkey 27 | }; 28 | WebIM.conn.open(options); 29 | }) 30 | //退出 31 | $('#logout').click(function () { 32 | WebIM.conn.close(); 33 | }); 34 | }) 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /simpleDemo/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSttBv1xiRD4/m 3 | ps+QH5SNFYmsE7vgzMsTklb7ylXHcTWZC4Uzv2du/poM3UwJX9QrPHUkzSlXYK8R 4 | Uq+KsgYuhhNeAG31+TmjOhEdpG/rrUtYcrdMROvBiH2QRruNqHIv48DEMAMX9M9C 5 | S6NBmUT15ot62TE+A/bAFpbw1Hdx0vcAYdFWCJjNuqekT1/shhRBUbiUiA348OV4 6 | ws1sDDvM08PVMzZDok2FUImr/s96OtXKpa7rcPVPHzr42oybDLjP1Bsp4KgZN+Mu 7 | v9FX1aZudi0lauqShl/RS5V/I9oTdxTLXE9frsevIqBEeNqz4I892RRKn8ne2Kwn 8 | G3rCK805AgMBAAECggEAah0xywKPMTRJR3E1RS+iORzv03b+d4yIASLiKQFiY3td 9 | YdF0SUZSIHSjpVRWD52xDoiOzcxZ72ntlaDxeGZklDfDQEUdQ0A8UPPJt1/c12Ai 10 | 09k8/DwJLJR1BOoz8zR3Sm33Wa7EPgqB8ZhVfzfFKfnSvkrjE9C4Ipz0FUIHFlqA 11 | lC7BvgH61qTTzHtsolOXvr3hGfvuHUZ4WctubSd2lBIgD6AaYjT9nwA04OuKgsN0 12 | Z2h59Mhma61ZYsrzK5Jfu8zKvoQaI/R6q2IpUVKTTtG16m0LVfUoIKLYmqBi+RkB 13 | 32pj1h24PPk82RogCpMFxEFM86nf2zaynULLYtTwlQKBgQDDJVF5bZciPB3FIU5W 14 | U+PJGfaZVKSOi4/dzI1BrEUpvzLoHMaWD6FaUE/3lBWOAtkQowEjA3i0fpbFTIbX 15 | ETGQ21MEtYlU8Z/8+sEGLblcc70pvnGfVW4jgcEqeAOC86T1JOxDcN4LTWydNAGM 16 | vVedfIl9hTIPDftiRrhLGdhz/wKBgQDAdyYFty3j2sdASzJ1fOqLTUKi0UaxQ5tL 17 | iW38294OtnONBFSoRSjpNHaM3sZKANSnT6hMTOLHXjIIDGHvUtB2AsqvmIxmT4zN 18 | gZ2Q3cx8ARRK3o6WarOALsvUloQI2VHcorcUv+FJhUcSNicIYFGVxLUkpgj+Fj6d 19 | tlcup65exwKBgQCPxcLND6y1kEzum6c3ev7rfrFsLiw+yRvpcXbw15btNuyujeTR 20 | Uhqyi1EfOn7W7co2s6F2xmv7rvgtzD/b29MEwlHbulO/vXT90VC82JskPjEzvBfC 21 | KBLJHiPOwjT0GcWCxv06gsFU2moXN+WcZzNR2BsD20oSRV7b/PZyfkw2yQKBgHkR 22 | Ogi0XpiLAXVvF/GRv1QbKISU0jwlg+VzMI2znK5ylKMrCznCL2bqynv4kpDwjtQk 23 | JoDAiJLSrPQlHeHWnSzuuqLUedTNy4tmMkBzVPLYFvS1pMn3mqxSQ4OZnrqWQYlh 24 | jXru7H876CYLjGgXdpqYXz4Ld3KleYPptFOzxNr3AoGAG+TOWBF7Fu0y5cLeLkbZ 25 | S0Zhh0+78KQpgZObHGkDu6cfSO2N8t/0mZtDrokoCLZ6xBDvpk2UIdKt190QHzM3 26 | qa0NsHfbHhgUKV7r70UNC4+O7NmTFVQ9Mcne9IBfl7I7Fz/ir2NWYO0MlWSfMflH 27 | BD0J9+6ypVsSfqPF+kuUow8= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /simpleDemo/readme.md: -------------------------------------------------------------------------------- 1 | ## 说明 2 | + 简单demo提供最简单的集成sdk方式,可以直接调用api进行测试,兼容绝大部分浏览器(ie9+) 3 | + sdk文件夹下 webimSDK为即时通讯sdk, EMedia_x1v1为单人 + 多人音视频sdk,同时EMedia_x1v1依赖webimSDK, 音视频必须用https 4 | + WebIMConfig为webimSDK所以需要的配置文件, cert.pem、key.pem为起https服务需要的证书 5 | + IE浏览器文档模式选择标准模式 6 | + 会议模式测试,是登陆者先创建会议,邀请成员加入后再发起共享 7 | ##### 运行 8 | ## 安装环境 9 | 1. sudo npm i -g http-server 10 | 11 | 12 | ## http 启动,不支持桌面共享 13 | 2. http-server 14 | 15 | ## mac 本地 https 启动命令, 先填写信息创建证书,然后再启动,支持共享桌面 16 | 3. openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem 17 | 18 | http-server -S -C cert.pem -o 19 | 20 | 21 | 4. 用浏览器打开服务地址 -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | echo TAG: $TRAVIS_TAG 2 | echo nexus_auth: ${nexus_auth} 3 | 4 | packing(){ 5 | cd ./demo 6 | echo $PWD 7 | npm install 8 | cd ../ 9 | echo -e "\nINSTALL DONE.\n" 10 | 11 | cd ./demo 12 | TRAVIS=true TAG_NAME=$TRAVIS_TAG npm run build 13 | cd ../ 14 | echo -e "\nBUILD DONE.\n" 15 | sed -i "s/{#version}/${TRAVIS_TAG}/g" ./demo/build/index.html 16 | } 17 | 18 | upload(){ 19 | # 为了不修改 ci,copy 一份 20 | echo -e "\nCOPY files...\n" 21 | cp -r ./demo/build ./chatdemo-webim 22 | 23 | echo -e "\nZIP files...\n" 24 | zip -r $TRAVIS_TAG.zip chatdemo-webim 25 | 26 | UPLOAD_PARAMS="-v -F r=releases -F hasPom=false -F e=zip -F g=com.easemob.chatdemo2 -F a=webim -F v="$TRAVIS_TAG" -F p=zip -F file=@"$TRAVIS_TAG".zip -u ci-deploy:${EASEMOB_NEXUS_PASSWORD} " 27 | UPLOAD_URL="https://hk.nexus.op.easemob.com/nexus/service/local/artifact/maven/content" 28 | echo -e "\nUPLOAD ZIP..." 29 | echo -e $UPLOAD_PARAMS"\n"$UPLOAD_URL"\n" 30 | curl $UPLOAD_PARAMS $UPLOAD_URL 31 | 32 | # curl 33 | # -v -F r=releases -F hasPom=false -F e=zip -F g=com.easemob.im.fe.rs -F a=im-console -F v=$TRAVIS_TAG -F p=zip -F file=@console.zip -u ci-deploy:Xyc-R5c-SdS-2Qr 34 | # http://hk.nexus.op.easemob.com/nexus/service/local/artifact/maven/content 35 | } 36 | 37 | 38 | if [ $TRAVIS_TAG ]; then 39 | echo -e "\n[is a tag] start packing\n" 40 | packing || exit 1 41 | upload 42 | else 43 | echo -e "\n[not a tag] exit packing\n" 44 | fi --------------------------------------------------------------------------------