Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
Madara
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
viitto
Madara
Commits
665ae7bc
Commit
665ae7bc
authored
Sep 30, 2019
by
罗超
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
修复BUG
parent
c5a1a5e2
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
525 additions
and
467 deletions
+525
-467
index.js
src/configs/index.js
+2
-2
index.js
src/main/index.js
+4
-3
chat.vue
src/renderer/components/msssage/chat.vue
+14
-11
chateditor.vue
src/renderer/components/msssage/chateditor.vue
+1
-0
index.vue
src/renderer/components/msssage/index.vue
+26
-3
initNimSDK.js
src/store/actions/initNimSDK.js
+128
-120
msgs.js
src/store/actions/msgs.js
+4
-0
index.js
src/utils/index.js
+346
-328
No files found.
src/configs/index.js
View file @
665ae7bc
...
...
@@ -20,8 +20,8 @@ let config = {
// 我的手机图标
myPhoneIcon
:
'http://yx-web.nos.netease.com/webdoc/h5/im/my-phone.png'
,
// 本地消息显示数量,会影响性能
localMsglimit
:
36
,
useDb
:
fals
e
localMsglimit
:
10
,
useDb
:
tru
e
}
const
env
=
'online'
...
...
src/main/index.js
View file @
665ae7bc
...
...
@@ -299,7 +299,7 @@ ipc.on('reconnect', function(event) {
ipc.on('
loginSuccess
', function(event, userInfo) {
initNIM(userInfo.ImAccount, userInfo.ImToken)
registScrollerHotKey()
getScrollerhotWindows()
//
getScrollerhotWindows()
global.LOGINUSER = userInfo
getNewMsgWindows()
getMainWinodw(0)
...
...
@@ -454,7 +454,7 @@ ipc.on('openSystemNotice', function(event, sessionid) {
show: false,
width: 320,
height: 190,
alwaysOnTop:
tru
e,
alwaysOnTop:
fals
e,
focusable: false,
icon: windowIcon
})
...
...
@@ -715,7 +715,7 @@ ipc.on('send-file', function(event, obj) {
}
})
})
ipc.on('
read
-
clip
', function
(event) {
ipc.on('
read
-
clip
', function(event) {
const rawFilePath = clipboard.readBuffer('
FileNameW
').toString('
ucs2
');
let filePath = rawFilePath.replace(new RegExp(String.fromCharCode(0), '
g
'), '');
if (filePath && filePath.length > 0) {
...
...
@@ -950,6 +950,7 @@ let getNoticeWindows = function() {
})
}
noticeWindows
.
loadURL
(
noticeUrl
)
mainWindow
.
setFocusable
(
true
)
noticeWindows
.
setMenu
(
null
)
noticeWindows
.
once
(
'ready-to-show'
,
()
=>
{
noticeWindows
.
show
()
...
...
src/renderer/components/msssage/chat.vue
View file @
665ae7bc
...
...
@@ -331,12 +331,13 @@ export default {
}
else
if
(
/^team-/
.
test
(
sessionId
))
{
var
teamMembers
=
this
.
$store
.
state
.
teamMembers
[
this
.
to
];
if
(
this
.
teamInfo
)
{
if
(
teamMembers
===
undefined
||
teamMembers
.
length
<
this
.
teamInfo
.
memberNum
)
{
this
.
$store
.
dispatch
(
"getTeamMembers"
,
this
.
to
);
}
// if (
// teamMembers === undefined ||
// teamMembers.length
<
this
.
teamInfo
.
memberNum
// ) {
// console.log('...............')
// this.$store.dispatch("getTeamMembers", this.to);
// }
if
(
this
.
teamInfo
)
{
// teamInfo中的人数为初始获取的值,在人员增减后不会及时更新,而teamMembers在人员增减后同步维护的人员信息
var
members
=
...
...
@@ -395,15 +396,16 @@ export default {
}
else
{
this
.
lstShowData
.
lastMsgShow
=
""
;
}
//
}
if
(
msgs
.
length
>
0
)
this
.
lstTimer
=
msgs
[
msgs
.
length
-
1
].
time
if
(
this
.
scene
==
'team'
){
msgs
=
msgs
.
filter
(
x
=>
{
if
(
x
.
type
!=
'notification'
||
(
x
.
type
==
'notification'
&&
!
x
.
attach
.
team
.
custom
))
return
x
return
;
})
//
msgs=msgs.filter(x=>{
//
if(x.type!='notification' ||(x.type=='notification' && !x.attach.team.custom))
//
return x
//
return;
//
})
}
// console.log(msgs)
return
msgs
;
...
...
@@ -885,6 +887,7 @@ export default {
height
:
46px
;
line-height
:
46px
;
padding-left
:
20px
;
padding-right
:
50px
;
border-bottom
:
1px
solid
#ddd
;
position
:
relative
;
}
...
...
src/renderer/components/msssage/chateditor.vue
View file @
665ae7bc
...
...
@@ -408,6 +408,7 @@ export default {
callbackTeam
:
this
.
callBackTeam
});
}
else
{
console
.
log
(
'in start...........'
,
new
Date
())
this
.
$store
.
dispatch
(
"sendMsg"
,
{
type
:
"text"
,
scene
:
this
.
scene
,
...
...
src/renderer/components/msssage/index.vue
View file @
665ae7bc
...
...
@@ -340,7 +340,26 @@ export default {
this
.
atIds
=
session
.
atId
session
.
atId
=
null
}
if
(
session
.
scene
===
"team"
)
{
var
teamMembers
=
this
.
$store
.
state
.
teamMembers
[
session
.
to
];
let
teamInfo
=
this
.
$store
.
state
.
teamlist
.
find
(
team
=>
{
return
team
.
teamId
===
this
.
currentSession
.
to
;
});
if
(
teamInfo
)
{
if
(
teamMembers
===
undefined
||
teamMembers
.
length
<
teamInfo
.
memberNum
)
{
this
.
$store
.
dispatch
(
"getTeamMembers"
,
session
.
to
);
}
}
}
this
.
$store
.
dispatch
(
"setCurrSession"
,
session
.
id
)
this
.
$store
.
dispatch
(
"getHistoryMsgs"
,
{
scene
:
session
.
scene
,
to
:
session
.
to
});
}
},
clearAtMe
(){
...
...
@@ -687,10 +706,14 @@ export default {
}
else
{
this
.
$electron
.
ipcRenderer
.
send
(
"newMessage"
,
[]);
}
if
(
!
this
.
$electron
.
remote
.
getCurrentWindow
().
isFocused
())
{
if
(
this
.
calcUnRead
(
this
.
newMsgList
,
unreadList
))
{
this
.
$electron
.
remote
.
getCurrentWindow
().
flashFrame
(
true
);
try
{
if
(
!
this
.
$electron
.
remote
.
getCurrentWindow
().
isFocused
())
{
if
(
this
.
calcUnRead
(
this
.
newMsgList
,
unreadList
))
{
this
.
$electron
.
remote
.
getCurrentWindow
().
flashFrame
(
true
);
}
}
}
catch
(
error
)
{
}
...
...
src/store/actions/initNimSDK.js
View file @
665ae7bc
...
...
@@ -6,130 +6,138 @@ import config from '../../configs/index'
import
pageUtil
from
'../../utils/page'
import
util
from
'../../utils/index'
import
store
from
'../'
import
{
onFriends
,
onSyncFriendAction
}
from
'./friends'
import
{
onRobots
}
from
'./robots'
import
{
onBlacklist
,
onMarkInBlacklist
}
from
'./blacks'
import
{
onMyInfo
,
onUserInfo
}
from
'./userInfo'
import
{
onSessions
,
onUpdateSession
}
from
'./session'
import
{
onRoamingMsgs
,
onOfflineMsgs
,
onMsg
,
onMutelist
,
onMarkInMutelist
}
from
'./msgs'
import
{
onSysMsgs
,
onSysMsg
,
onSysMsgUnread
,
onCustomSysMsgs
}
from
'./sysMsgs'
import
{
onTeams
,
onSynCreateTeam
,
onCreateTeam
,
onUpdateTeam
,
onTeamMembers
,
onUpdateTeamMember
,
onAddTeamMembers
,
onRemoveTeamMembers
,
onUpdateTeamManagers
,
onDismissTeam
,
onUpdateTeamMembersMute
,
onTeamMsgReceipt
}
from
'./team'
import
{
onFriends
,
onSyncFriendAction
}
from
'./friends'
import
{
onRobots
}
from
'./robots'
import
{
onBlacklist
,
onMarkInBlacklist
}
from
'./blacks'
import
{
onMyInfo
,
onUserInfo
}
from
'./userInfo'
import
{
onSessions
,
onUpdateSession
}
from
'./session'
import
{
onRoamingMsgs
,
onOfflineMsgs
,
onMsg
,
onMutelist
,
onMarkInMutelist
}
from
'./msgs'
import
{
onSysMsgs
,
onSysMsg
,
onSysMsgUnread
,
onCustomSysMsgs
}
from
'./sysMsgs'
import
{
onTeams
,
onSynCreateTeam
,
onCreateTeam
,
onUpdateTeam
,
onTeamMembers
,
onUpdateTeamMember
,
onAddTeamMembers
,
onRemoveTeamMembers
,
onUpdateTeamManagers
,
onDismissTeam
,
onUpdateTeamMembersMute
,
onTeamMsgReceipt
}
from
'./team'
const
SDK
=
require
(
'../../sdk/NIM_Web_SDK_v6.1.0'
)
// 重新初始化 NIM SDK
export
function
initNimSDK
({
state
,
commit
,
dispatch
},
loginInfo
)
{
if
(
state
.
nim
)
{
state
.
nim
.
disconnect
()
}
dispatch
(
'showLoading'
)
// 初始化SDK
window
.
nim
=
state
.
nim
=
SDK
.
NIM
.
getInstance
({
debug
:
false
,
appKey
:
config
.
appkey
,
account
:
loginInfo
.
uid
,
token
:
loginInfo
.
sdktoken
,
db
:
config
.
useDb
,
// logFunc: new SDK.NIM.LoggerPlugin({
// url: '/webdemo/h5/getlogger',
// level: 'info'
// }),
syncSessionUnread
:
true
,
syncRobots
:
true
,
autoMarkRead
:
true
,
// 默认为true
onconnect
:
function
onConnect
(
event
)
{
if
(
loginInfo
)
{
// 连接上以后更新uid
commit
(
'updateUserUID'
,
loginInfo
)
commit
(
'updateNetError'
,
false
)
console
.
log
(
'链接完成......'
)
}
},
onerror
:
function
onError
(
event
)
{
commit
(
'updateNetError'
,
true
)
},
onwillreconnect
:
function
onWillReconnect
()
{
commit
(
'updateNetError'
,
true
)
console
.
log
(
'尝试中.........'
)
},
ondisconnect
:
function
onDisconnect
(
error
)
{
switch
(
error
.
code
)
{
// 账号或者密码错误, 请跳转到登录页面并提示错误
case
302
:
pageUtil
.
turnPage
(
'帐号或密码错误'
,
'login'
)
break
// 被踢, 请提示错误后跳转到登录页面
case
'kicked'
:
let
map
=
{
PC
:
'电脑版'
,
Web
:
'网页版'
,
Android
:
'手机版'
,
iOS
:
'手机版'
,
WindowsPhone
:
'手机版'
}
let
str
=
error
.
from
let
errorMsg
=
`你的帐号于
${
util
.
formatDate
(
new
Date
())}
被
${(
map
[
str
]
||
'其他端'
)}
踢出下线,请确定帐号信息安全!`
pageUtil
.
turnPage
(
errorMsg
,
'login'
)
break
default
:
break
}
},
// // 多端登录
// onloginportschange: onLoginPortsChange,
// 用户关系及好友关系
onblacklist
:
onBlacklist
,
onsyncmarkinblacklist
:
onMarkInBlacklist
,
onmutelist
:
onMutelist
,
onsyncmarkinmutelist
:
onMarkInMutelist
,
onfriends
:
onFriends
,
onsyncfriendaction
:
onSyncFriendAction
,
// 机器人
onrobots
:
onRobots
,
// 用户名片 - actions/userInfo
onmyinfo
:
onMyInfo
,
onupdatemyinfo
:
onMyInfo
,
onusers
:
onUserInfo
,
onupdateuser
:
onUserInfo
,
// // 群组
onteams
:
onTeams
,
onsynccreateteam
:
onSynCreateTeam
,
syncTeams
:
true
,
onteammembers
:
onTeamMembers
,
onCreateTeam
:
onCreateTeam
,
onDismissTeam
:
onDismissTeam
,
onUpdateTeam
:
onUpdateTeam
,
onAddTeamMembers
:
onAddTeamMembers
,
onRemoveTeamMembers
:
onRemoveTeamMembers
,
onUpdateTeamManagers
:
onUpdateTeamManagers
,
onupdateteammember
:
onUpdateTeamMember
,
onUpdateTeamMembersMute
:
onUpdateTeamMembersMute
,
onTeamMsgReceipt
:
onTeamMsgReceipt
,
// // 会话
onsessions
:
onSessions
,
onupdatesession
:
onUpdateSession
,
// // 消息
onroamingmsgs
:
onRoamingMsgs
,
onofflinemsgs
:
onOfflineMsgs
,
onmsg
:
onMsg
,
// // 系统通知
onsysmsg
:
onSysMsg
,
onofflinesysmsgs
:
onSysMsgs
,
onupdatesysmsg
:
onSysMsg
,
// 通过、拒绝好友申请会收到此回调
if
(
state
.
nim
)
{
state
.
nim
.
disconnect
()
}
dispatch
(
'showLoading'
)
// 初始化SDK
window
.
nim
=
state
.
nim
=
SDK
.
NIM
.
getInstance
({
debug
:
false
,
appKey
:
config
.
appkey
,
account
:
loginInfo
.
uid
,
token
:
loginInfo
.
sdktoken
,
db
:
config
.
useDb
,
// logFunc: new SDK.NIM.LoggerPlugin({
// url: '/webdemo/h5/getlogger',
// level: 'info'
// }),
syncSessionUnread
:
true
,
syncRobots
:
true
,
autoMarkRead
:
true
,
// 默认为true
onconnect
:
function
onConnect
(
event
)
{
if
(
loginInfo
)
{
// 连接上以后更新uid
commit
(
'updateUserUID'
,
loginInfo
)
commit
(
'updateNetError'
,
false
)
console
.
log
(
'链接完成......'
)
}
},
onerror
:
function
onError
(
event
)
{
commit
(
'updateNetError'
,
true
)
},
onwillreconnect
:
function
onWillReconnect
()
{
commit
(
'updateNetError'
,
true
)
console
.
log
(
'尝试中.........'
)
},
ondisconnect
:
function
onDisconnect
(
error
)
{
switch
(
error
.
code
)
{
// 账号或者密码错误, 请跳转到登录页面并提示错误
case
302
:
pageUtil
.
turnPage
(
'帐号或密码错误'
,
'login'
)
break
// 被踢, 请提示错误后跳转到登录页面
case
'kicked'
:
let
map
=
{
PC
:
'电脑版'
,
Web
:
'网页版'
,
Android
:
'手机版'
,
iOS
:
'手机版'
,
WindowsPhone
:
'手机版'
}
let
str
=
error
.
from
let
errorMsg
=
`你的帐号于
${
util
.
formatDate
(
new
Date
())}
被
${(
map
[
str
]
||
'其他端'
)}
踢出下线,请确定帐号信息安全!`
pageUtil
.
turnPage
(
errorMsg
,
'login'
)
break
default
:
break
}
},
// // 多端登录
// onloginportschange: onLoginPortsChange,
// 用户关系及好友关系
onblacklist
:
onBlacklist
,
onsyncmarkinblacklist
:
onMarkInBlacklist
,
onmutelist
:
onMutelist
,
onsyncmarkinmutelist
:
onMarkInMutelist
,
onfriends
:
onFriends
,
onsyncfriendaction
:
onSyncFriendAction
,
// 机器人
onrobots
:
onRobots
,
// 用户名片 - actions/userInfo
onmyinfo
:
onMyInfo
,
onupdatemyinfo
:
onMyInfo
,
onusers
:
onUserInfo
,
onupdateuser
:
onUserInfo
,
// // 群组
onteams
:
onTeams
,
onsynccreateteam
:
onSynCreateTeam
,
syncTeams
:
true
,
onteammembers
:
onTeamMembers
,
onCreateTeam
:
onCreateTeam
,
onDismissTeam
:
onDismissTeam
,
onUpdateTeam
:
onUpdateTeam
,
onAddTeamMembers
:
onAddTeamMembers
,
onRemoveTeamMembers
:
onRemoveTeamMembers
,
onUpdateTeamManagers
:
onUpdateTeamManagers
,
onupdateteammember
:
onUpdateTeamMember
,
onUpdateTeamMembersMute
:
onUpdateTeamMembersMute
,
onTeamMsgReceipt
:
onTeamMsgReceipt
,
// // 会话
onsessions
:
onSessions
,
onupdatesession
:
onUpdateSession
,
// // 消息
onroamingmsgs
:
onRoamingMsgs
,
onofflinemsgs
:
onOfflineMsgs
,
onmsg
:
onMsg
,
// // 系统通知
onsysmsg
:
onSysMsg
,
onofflinesysmsgs
:
onSysMsgs
,
onupdatesysmsg
:
onSysMsg
,
// 通过、拒绝好友申请会收到此回调
onsysmsgunread
:
onSysMsgUnread
,
onupdatesysmsgunread
:
onSysMsgUnread
,
onsysmsgunread
:
onSysMsgUnread
,
onupdatesysmsgunread
:
onSysMsgUnread
,
onofflinecustomsysmsgs
:
onCustomSysMsgs
,
oncustomsysmsg
:
onCustomSysMsgs
,
// // 同步完成
onsyncdone
:
function
onSyncDone
()
{
dispatch
(
'hideLoading'
)
// 说明在聊天列表页
if
(
store
.
state
.
currSessionId
)
{
dispatch
(
'setCurrSession'
,
store
.
state
.
currSessionId
)
}
}
})
window
.
nim
.
useDb
=
config
.
useDb
}
onofflinecustomsysmsgs
:
onCustomSysMsgs
,
oncustomsysmsg
:
onCustomSysMsgs
,
// // 同步完成
onsyncdone
:
function
onSyncDone
()
{
dispatch
(
'hideLoading'
)
// 说明在聊天列表页
if
(
store
.
state
.
currSessionId
)
{
dispatch
(
'setCurrSession'
,
store
.
state
.
currSessionId
)
}
},
shouldIgnoreNotification
:
function
(
msg
)
{
let
team
=
msg
.
attach
.
team
if
(
team
[
'name'
]
||
team
[
'intro'
]
||
team
[
'joinMode'
]
||
team
[
'inviteMode'
]
||
team
[
'updateTeamMode'
]
||
team
[
'beInviteMode'
])
{
return
false
}
else
{
return
true
}
}
})
window
.
nim
.
useDb
=
config
.
useDb
}
\ No newline at end of file
src/store/actions/msgs.js
View file @
665ae7bc
...
...
@@ -137,6 +137,8 @@ function onSendMsgDone(error, msg) {
}
})
}
console
.
log
(
'end sdk.........'
)
util
.
getDate
()
onMsg
(
msg
)
}
...
...
@@ -231,6 +233,8 @@ export function sendMsg({ state, commit }, obj) {
done
:
onSendMsgDone
,
needMsgReceipt
:
obj
.
needMsgReceipt
||
false
})
console
.
log
(
'start sdk.........'
)
util
.
getDate
()
break
case
'at'
:
nim
.
sendText
({
...
...
src/utils/index.js
View file @
665ae7bc
import
Vue
from
'vue'
import
store
from
'../store'
import
{
utils
}
from
'mocha'
;
if
(
!
Function
.
prototype
.
bind
){
Function
.
prototype
.
bind
=
function
(){
var
fn
=
this
,
args
=
Array
.
prototype
.
slice
.
call
(
arguments
),
object
=
args
.
shift
();
return
function
(){
return
fn
.
apply
(
object
,
args
.
concat
(
Array
.
prototype
.
slice
.
call
(
arguments
)));
if
(
!
Function
.
prototype
.
bind
)
{
Function
.
prototype
.
bind
=
function
()
{
var
fn
=
this
,
args
=
Array
.
prototype
.
slice
.
call
(
arguments
),
object
=
args
.
shift
();
return
function
()
{
return
fn
.
apply
(
object
,
args
.
concat
(
Array
.
prototype
.
slice
.
call
(
arguments
)));
}
}
}
}
let
Utils
=
Object
.
create
(
null
)
Utils
.
encode
=
function
(
_map
,
_content
)
{
_content
=
''
+
_content
if
(
!
_map
||
!
_content
)
{
return
_content
||
''
}
return
_content
.
replace
(
_map
.
r
,
function
(
$1
)
{
var
_result
=
_map
[
!
_map
.
i
?
$1
.
toLowerCase
()
:
$1
]
return
_result
!=
null
?
_result
:
$1
});
Utils
.
encode
=
function
(
_map
,
_content
)
{
_content
=
''
+
_content
if
(
!
_map
||
!
_content
)
{
return
_content
||
''
}
return
_content
.
replace
(
_map
.
r
,
function
(
$1
)
{
var
_result
=
_map
[
!
_map
.
i
?
$1
.
toLowerCase
()
:
$1
]
return
_result
!=
null
?
_result
:
$1
});
};
Utils
.
escape
=
(
function
()
{
let
_reg
=
/<br
\/?
>$/
let
_map
=
{
r
:
/
\<
|
\>
|
\&
|
\r
|
\n
|
\s
|
\'
|
\"
/g
,
'<'
:
'<'
,
'>'
:
'>'
,
'&'
:
'&'
,
' '
:
' '
,
'"'
:
'"'
,
"'"
:
'''
,
'
\
n'
:
'<br/>'
,
'
\
r'
:
''
}
return
function
(
_content
)
{
_content
=
Utils
.
encode
(
_map
,
_content
)
return
_content
.
replace
(
_reg
,
'<br/>'
);
};
Utils
.
escape
=
(
function
()
{
let
_reg
=
/<br
\/?
>$/
let
_map
=
{
r
:
/
\<
|
\>
|
\&
|
\r
|
\n
|
\s
|
\'
|
\"
/g
,
'<'
:
'<'
,
'>'
:
'>'
,
'&'
:
'&'
,
' '
:
' '
,
'"'
:
'"'
,
"'"
:
'''
,
'
\
n'
:
'<br/>'
,
'
\
r'
:
''
}
return
function
(
_content
)
{
_content
=
Utils
.
encode
(
_map
,
_content
)
return
_content
.
replace
(
_reg
,
'<br/>'
);
};
})();
Utils
.
object2query
=
function
(
obj
)
{
let
keys
=
Object
.
keys
(
obj
)
let
queryArray
=
keys
.
map
(
item
=>
{
return
`
${
item
}
=
${
encodeURIComponent
(
obj
[
item
])}
`
})
return
queryArray
.
join
(
'&'
)
Utils
.
object2query
=
function
(
obj
)
{
let
keys
=
Object
.
keys
(
obj
)
let
queryArray
=
keys
.
map
(
item
=>
{
return
`
${
item
}
=
${
encodeURIComponent
(
obj
[
item
])}
`
})
return
queryArray
.
join
(
'&'
)
}
Utils
.
getDate
=
function
()
{
console
.
log
(
new
Date
());
}
// https://cn.vuejs.org/v2/guide/reactivity.html
// Vue 不能检测到对象属性的添加或删除。然而它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上
Utils
.
mergeObject
=
function
(
dest
,
src
)
{
if
(
typeof
dest
!==
'object'
||
dest
===
null
)
{
dest
=
Object
.
create
(
null
)
}
dest
=
Object
.
assign
(
Object
.
create
(
null
),
dest
,
src
)
return
dest
Utils
.
mergeObject
=
function
(
dest
,
src
)
{
if
(
typeof
dest
!==
'object'
||
dest
===
null
)
{
dest
=
Object
.
create
(
null
)
}
dest
=
Object
.
assign
(
Object
.
create
(
null
),
dest
,
src
)
return
dest
}
Utils
.
mergeVueObject
=
function
(
dest
,
src
)
{
let
keys
=
Object
.
keys
(
src
)
keys
.
forEach
(
item
=>
{
if
(
typeof
src
[
item
]
!==
'undefined'
)
{
Vue
.
set
(
dest
,
item
,
src
[
item
])
}
})
return
dest
Utils
.
mergeVueObject
=
function
(
dest
,
src
)
{
let
keys
=
Object
.
keys
(
src
)
keys
.
forEach
(
item
=>
{
if
(
typeof
src
[
item
]
!==
'undefined'
)
{
Vue
.
set
(
dest
,
item
,
src
[
item
])
}
})
return
dest
}
// 消息类型列表
Utils
.
mapMsgType
=
function
(
msg
)
{
let
map
=
{
text
:
'文本消息'
,
image
:
'图片消息'
,
file
:
'文件消息'
,
audio
:
'语音消息'
,
video
:
'视频消息'
,
geo
:
'地理位置消息'
,
tip
:
'提醒消息'
,
custom
:
'自定义消息'
,
notification
:
'系统通知'
,
robot
:
'机器人消息'
}
let
type
=
msg
.
type
return
map
[
type
]
||
'未知消息类型'
Utils
.
mapMsgType
=
function
(
msg
)
{
let
map
=
{
text
:
'文本消息'
,
image
:
'图片消息'
,
file
:
'文件消息'
,
audio
:
'语音消息'
,
video
:
'视频消息'
,
geo
:
'地理位置消息'
,
tip
:
'提醒消息'
,
custom
:
'自定义消息'
,
notification
:
'系统通知'
,
robot
:
'机器人消息'
}
let
type
=
msg
.
type
return
map
[
type
]
||
'未知消息类型'
}
Utils
.
stringifyDate
=
function
(
datetime
,
simple
=
false
)
{
// let weekMap = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
let
weekMap
=
[
'星期日'
,
'星期一'
,
'星期二'
,
'星期三'
,
'星期四'
,
'星期五'
,
'星期六'
]
datetime
=
new
Date
(
datetime
)
let
year
=
datetime
.
getFullYear
()
let
simpleYear
=
datetime
.
getYear
()
-
100
let
month
=
datetime
.
getMonth
()
+
1
month
=
month
>
9
?
month
:
'0'
+
month
let
day
=
datetime
.
getDate
()
day
=
day
>
9
?
day
:
'0'
+
day
let
hour
=
datetime
.
getHours
()
hour
=
hour
>
9
?
hour
:
'0'
+
hour
let
min
=
datetime
.
getMinutes
()
min
=
min
>
9
?
min
:
'0'
+
min
let
week
=
datetime
.
getDay
()
week
=
weekMap
[
week
]
let
thatDay
=
(
new
Date
(
year
,
month
-
1
,
day
,
0
,
0
,
0
)).
getTime
()
Utils
.
stringifyDate
=
function
(
datetime
,
simple
=
false
)
{
// let weekMap = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
let
weekMap
=
[
'星期日'
,
'星期一'
,
'星期二'
,
'星期三'
,
'星期四'
,
'星期五'
,
'星期六'
]
datetime
=
new
Date
(
datetime
)
let
year
=
datetime
.
getFullYear
()
let
simpleYear
=
datetime
.
getYear
()
-
100
let
month
=
datetime
.
getMonth
()
+
1
month
=
month
>
9
?
month
:
'0'
+
month
let
day
=
datetime
.
getDate
()
day
=
day
>
9
?
day
:
'0'
+
day
let
hour
=
datetime
.
getHours
()
hour
=
hour
>
9
?
hour
:
'0'
+
hour
let
min
=
datetime
.
getMinutes
()
min
=
min
>
9
?
min
:
'0'
+
min
let
week
=
datetime
.
getDay
()
week
=
weekMap
[
week
]
let
thatDay
=
(
new
Date
(
year
,
month
-
1
,
day
,
0
,
0
,
0
)).
getTime
()
if
(
simple
)
{
return
{
withYear
:
`
${
day
}
/
${
month
}
/
${
simpleYear
}
`
,
withMonth
:
`
${
month
}
-
${
day
}
`
,
withDay
:
`
${
week
}
`
,
withLastDay
:
`昨天`
,
withHour
:
`
${
hour
}
:
${
min
}
`
,
thatDay
if
(
simple
)
{
return
{
withYear
:
`
${
day
}
/
${
month
}
/
${
simpleYear
}
`
,
withMonth
:
`
${
month
}
-
${
day
}
`
,
withDay
:
`
${
week
}
`
,
withLastDay
:
`昨天`
,
withHour
:
`
${
hour
}
:
${
min
}
`
,
thatDay
}
}
else
{
return
{
withYear
:
`
${
year
}
-
${
month
}
-
${
day
}
${
hour
}
:
${
min
}
`
,
withMonth
:
`
${
month
}
-
${
day
}
${
hour
}
:
${
min
}
`
,
withDay
:
`
${
week
}
${
hour
}
:
${
min
}
`
,
withLastDay
:
`昨天
${
hour
}
:
${
min
}
`
,
withHour
:
`
${
hour
}
:
${
min
}
`
,
thatDay
}
}
}
else
{
return
{
withYear
:
`
${
year
}
-
${
month
}
-
${
day
}
${
hour
}
:
${
min
}
`
,
withMonth
:
`
${
month
}
-
${
day
}
${
hour
}
:
${
min
}
`
,
withDay
:
`
${
week
}
${
hour
}
:
${
min
}
`
,
withLastDay
:
`昨天
${
hour
}
:
${
min
}
`
,
withHour
:
`
${
hour
}
:
${
min
}
`
,
thatDay
}
}
}
/* 格式化日期 */
Utils
.
formatDate
=
function
(
datetime
,
simple
=
false
)
{
let
tempDate
=
(
new
Date
()).
getTime
()
let
result
=
this
.
stringifyDate
(
datetime
,
simple
)
let
thatDay
=
result
.
thatDay
let
deltaTime
=
(
tempDate
-
thatDay
)
/
1000
Utils
.
formatDate
=
function
(
datetime
,
simple
=
false
)
{
let
tempDate
=
(
new
Date
()).
getTime
()
let
result
=
this
.
stringifyDate
(
datetime
,
simple
)
let
thatDay
=
result
.
thatDay
let
deltaTime
=
(
tempDate
-
thatDay
)
/
1000
if
(
deltaTime
<
3600
*
24
)
{
return
result
.
withHour
}
else
if
(
deltaTime
<
3600
*
24
*
2
)
{
return
result
.
withLastDay
}
else
if
(
deltaTime
<
3600
*
24
*
7
)
{
return
result
.
withDay
}
else
if
(
deltaTime
<
3600
*
24
*
30
)
{
return
result
.
withMonth
}
else
{
return
result
.
withYear
}
if
(
deltaTime
<
3600
*
24
)
{
return
result
.
withHour
}
else
if
(
deltaTime
<
3600
*
24
*
2
)
{
return
result
.
withLastDay
}
else
if
(
deltaTime
<
3600
*
24
*
7
)
{
return
result
.
withDay
}
else
if
(
deltaTime
<
3600
*
24
*
30
)
{
return
result
.
withMonth
}
else
{
return
result
.
withYear
}
}
Utils
.
parseSession
=
function
(
sessionId
)
{
if
(
/^p2p-/
.
test
(
sessionId
))
{
return
{
scene
:
'p2p'
,
to
:
sessionId
.
replace
(
/^p2p-/
,
''
)
}
}
else
if
(
/^team-/
.
test
(
sessionId
))
{
return
{
scene
:
'team'
,
to
:
sessionId
.
replace
(
/^team-/
,
''
)
Utils
.
parseSession
=
function
(
sessionId
)
{
if
(
/^p2p-/
.
test
(
sessionId
))
{
return
{
scene
:
'p2p'
,
to
:
sessionId
.
replace
(
/^p2p-/
,
''
)
}
}
else
if
(
/^team-/
.
test
(
sessionId
))
{
return
{
scene
:
'team'
,
to
:
sessionId
.
replace
(
/^team-/
,
''
)
}
}
}
}
Utils
.
parseCustomMsg
=
function
(
msg
)
{
if
(
msg
.
type
===
'custom'
)
{
try
{
let
cnt
=
JSON
.
parse
(
msg
.
content
)
switch
(
cnt
.
type
)
{
case
1
:
return
'[猜拳消息]'
case
2
:
return
'[阅后即焚]'
case
3
:
return
'[贴图表情]'
case
4
:
return
'[白板消息]'
}
}
catch
(
e
)
{}
return
'[自定义消息]'
}
return
''
}
/* 获得有效的备注名 */
Utils
.
getFriendAlias
=
function
(
userInfo
)
{
userInfo
.
alias
=
userInfo
.
alias
?
userInfo
.
alias
.
trim
()
:
''
return
userInfo
.
alias
||
userInfo
.
nick
||
userInfo
.
account
Utils
.
parseCustomMsg
=
function
(
msg
)
{
if
(
msg
.
type
===
'custom'
)
{
try
{
let
cnt
=
JSON
.
parse
(
msg
.
content
)
switch
(
cnt
.
type
)
{
case
1
:
return
'[猜拳消息]'
case
2
:
return
'[阅后即焚]'
case
3
:
return
'[贴图表情]'
case
4
:
return
'[白板消息]'
}
}
catch
(
e
)
{}
return
'[自定义消息]'
}
return
''
}
/* 获得有效的备注名 */
Utils
.
getFriendAlias
=
function
(
userInfo
)
{
userInfo
.
alias
=
userInfo
.
alias
?
userInfo
.
alias
.
trim
()
:
''
return
userInfo
.
alias
||
userInfo
.
nick
||
userInfo
.
account
}
Utils
.
generateChatroomSysMsg
=
function
(
data
)
{
let
text
switch
(
data
.
attach
.
type
)
{
case
'memberEnter'
:
text
=
`欢迎
${
data
.
attach
.
fromNick
}
进入直播间`
break
case
'memberExit'
:
text
=
`
${
data
.
attach
.
fromNick
}
离开了直播间`
break
case
'blackMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员拉入黑名单`
break
case
'unblackMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员解除拉黑`
break
case
'gagMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员禁言`
break
case
'ungagMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员解除禁言`
break
case
'addManager'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被任命管理员身份`
break
case
'removeManager'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被解除管理员身份`
break
;
case
'addTempMute'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员临时禁言`
break
;
case
'removeTempMute'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员解除临时禁言`
break
;
case
'addCommon'
:
text
=
`管理员添加普通成员`
break
case
'removeCommon'
:
text
=
`管理员删除普通成员`
break
case
'kickMember'
:
text
=
`
${
data
.
attach
.
toNick
[
0
]}
被管理员踢出房间`
break
;
// case 'xxx':
// 直播公告已更新
// break;
default
:
text
=
'通知消息'
break
}
return
text
Utils
.
generateChatroomSysMsg
=
function
(
data
)
{
let
text
switch
(
data
.
attach
.
type
)
{
case
'memberEnter'
:
text
=
`欢迎
${
data
.
attach
.
fromNick
}
进入直播间`
break
case
'memberExit'
:
text
=
`
${
data
.
attach
.
fromNick
}
离开了直播间`
break
case
'blackMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员拉入黑名单`
break
case
'unblackMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员解除拉黑`
break
case
'gagMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员禁言`
break
case
'ungagMember'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员解除禁言`
break
case
'addManager'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被任命管理员身份`
break
case
'removeManager'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被解除管理员身份`
break
;
case
'addTempMute'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员临时禁言`
break
;
case
'removeTempMute'
:
text
=
`
${(
data
.
attach
.
toNick
[
0
]
||
data
.
attach
.
to
[
0
])}
被管理员解除临时禁言`
break
;
case
'addCommon'
:
text
=
`管理员添加普通成员`
break
case
'removeCommon'
:
text
=
`管理员删除普通成员`
break
case
'kickMember'
:
text
=
`
${
data
.
attach
.
toNick
[
0
]}
被管理员踢出房间`
break
;
// case 'xxx':
// 直播公告已更新
// break;
default
:
text
=
'通知消息'
break
}
return
text
}
Utils
.
generateTeamSysmMsg
=
function
(
data
)
{
var
text
,
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
switch
(
data
.
attach
.
type
)
{
case
'updateTeam'
:
text
=
this
.
getTeamUpdateInfo
(
data
)
break
;
case
'addTeamMembers'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
邀请
${
nicks
.
join
()}
加入群`
break
;
Utils
.
generateTeamSysmMsg
=
function
(
data
)
{
var
text
,
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
switch
(
data
.
attach
.
type
)
{
case
'updateTeam'
:
text
=
this
.
getTeamUpdateInfo
(
data
)
break
;
case
'addTeamMembers'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
邀请
${
nicks
.
join
()}
加入群`
break
;
}
case
'removeTeamMembers'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
nicks
.
join
()}
被
${
op
}
移出群`
break
;
}
case
'acceptTeamInvite'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
nicks
.
join
()}
接受了
${
op
}
入群邀请`
break
;
}
case
'passTeamApply'
:
{
let
op
=
nicks
.
shift
()
if
(
nicks
.
length
===
1
&&
op
===
nicks
[
0
])
{
// 此情况为高级群设置不需要验证,用户申请入群后,收到的群消息提示
text
=
`
${
op
}
加入群`
}
else
{
text
=
`
${
op
}
通过了
${
nicks
}
入群邀请`
}
break
;
}
case
'addTeamManagers'
:
{
// todo test
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
新增了
${
nicks
}
为管理员`
break
;
}
case
'removeTeamManagers'
:
{
// todo test
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
移除了
${
nicks
}
的管理员权限`
break
;
}
case
'leaveTeam'
:
{
text
=
`
${
nicks
.
join
()}
退出了群`
break
;
}
case
'dismissTeam'
:
{
text
=
`
${
nicks
.
join
()}
解散了群`
break
;
}
case
'transferTeam'
:
{
// todo test
let
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
let
op
=
nicks
.
shift
()
text
=
`
${
op
}
转让群主给
${
nicks
}
`
break
;
}
case
'updateTeamMute'
:
{
let
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
let
op
=
nicks
.
shift
()
text
=
`
${
nicks
}
被管理员
${
data
.
attach
.
mute
?
'禁言'
:
'解除禁言'
}
`
break
;
}
default
:
break
;
}
case
'removeTeamMembers'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
nicks
.
join
()}
被
${
op
}
移出群`
break
;
}
case
'acceptTeamInvite'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
nicks
.
join
()}
接受了
${
op
}
入群邀请`
break
;
}
case
'passTeamApply'
:
{
let
op
=
nicks
.
shift
()
if
(
nicks
.
length
===
1
&&
op
===
nicks
[
0
])
{
// 此情况为高级群设置不需要验证,用户申请入群后,收到的群消息提示
text
=
`
${
op
}
加入群`
}
else
{
text
=
`
${
op
}
通过了
${
nicks
}
入群邀请`
}
break
;
}
case
'addTeamManagers'
:
{
// todo test
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
新增了
${
nicks
}
为管理员`
break
;
}
case
'removeTeamManagers'
:
{
// todo test
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
移除了
${
nicks
}
的管理员权限`
break
;
}
case
'leaveTeam'
:
{
text
=
`
${
nicks
.
join
()}
退出了群`
break
;
}
case
'dismissTeam'
:
{
text
=
`
${
nicks
.
join
()}
解散了群`
break
;
}
case
'transferTeam'
:
{
// todo test
let
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
let
op
=
nicks
.
shift
()
text
=
`
${
op
}
转让群主给
${
nicks
}
`
break
;
}
case
'updateTeamMute'
:{
let
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
let
op
=
nicks
.
shift
()
text
=
`
${
nicks
}
被管理员
${
data
.
attach
.
mute
?
'禁言'
:
'解除禁言'
}
`
break
;
}
default
:
break
;
}
return
text
return
text
}
// todo 写成私有成员方法
Utils
.
getNickNames
=
function
(
users
)
{
return
users
.
map
(
user
=>
{
return
user
.
account
===
store
.
state
.
userUID
?
'你'
:
user
.
nick
})
return
users
.
map
(
user
=>
{
return
user
.
account
===
store
.
state
.
userUID
?
'你'
:
user
.
nick
})
}
// todo 写成私有成员方法
Utils
.
getTeamUpdateInfo
=
function
(
msg
)
{
let
text
,
team
=
msg
.
attach
.
team
,
op
=
this
.
getNickNames
(
msg
.
attach
.
users
).
pop
()
if
(
team
[
'name'
])
{
text
=
`
${
op
}
修改群名为
${
team
[
'name'
]}
`
}
else
if
(
team
[
'intro'
])
{
text
=
`
${
op
}
修改群介绍为
${
team
[
'intro'
]}
`
}
// 由于群公告的交互与 Android iOS 不一致,现版本不适配群公告
// else if (team['announcement']) {
// text = `${op}修改群公告为${team['announcement']}`
// }
else
if
(
team
[
'joinMode'
])
{
text
=
`群身份验证模式更新为
${
team
.
joinMode
===
'noVerify'
?
'不需要验证'
:
team
.
joinMode
===
'needVerify'
?
'需要验证'
:
'禁止任何人加入'
}
`
}
else
if
(
team
[
'inviteMode'
])
{
text
=
`邀请他人权限为
${
team
[
'inviteMode'
]
===
'all'
?
'所有人'
:
'管理员'
}
`
}
else
if
(
team
[
'updateTeamMode'
])
{
text
=
`群资料修改权限为
${
team
[
'updateTeamMode'
]
===
'all'
?
'所有人'
:
'管理员'
}
`
}
else
if
(
team
[
'beInviteMode'
])
{
text
=
`被邀请人身份
${
team
[
'beInviteMode'
]
===
'noVerify'
?
'不需要验证'
:
'需要验证'
}
`
}
else
{
text
=
'更新群信息'
}
return
text
let
text
,
team
=
msg
.
attach
.
team
,
op
=
this
.
getNickNames
(
msg
.
attach
.
users
).
pop
()
if
(
team
[
'name'
])
{
text
=
`
${
op
}
修改群名为
${
team
[
'name'
]}
`
}
else
if
(
team
[
'intro'
])
{
text
=
`
${
op
}
修改群介绍为
${
team
[
'intro'
]}
`
}
// 由于群公告的交互与 Android iOS 不一致,现版本不适配群公告
// else if (team['announcement']) {
// text = `${op}修改群公告为${team['announcement']}`
// }
else
if
(
team
[
'joinMode'
])
{
text
=
`群身份验证模式更新为
${
team
.
joinMode
===
'noVerify'
?
'不需要验证'
:
team
.
joinMode
===
'needVerify'
?
'需要验证'
:
'禁止任何人加入'
}
`
}
else
if
(
team
[
'inviteMode'
])
{
text
=
`邀请他人权限为
${
team
[
'inviteMode'
]
===
'all'
?
'所有人'
:
'管理员'
}
`
}
else
if
(
team
[
'updateTeamMode'
])
{
text
=
`群资料修改权限为
${
team
[
'updateTeamMode'
]
===
'all'
?
'所有人'
:
'管理员'
}
`
}
else
if
(
team
[
'beInviteMode'
])
{
text
=
`被邀请人身份
${
team
[
'beInviteMode'
]
===
'noVerify'
?
'不需要验证'
:
'需要验证'
}
`
}
else
{
text
=
'更新群信息'
}
return
text
}
Utils
.
showTips
=
function
(
msg
){
let
dom
=
document
.
querySelector
(
'#msg_tips'
)
if
(
dom
&&
dom
.
style
.
display
==
'none'
)
{
dom
.
innerText
=
msg
dom
.
style
.
display
=
'block'
setTimeout
(()
=>
{
dom
.
style
.
display
=
'none'
},
2000
);
}
else
{
//console.log('没有找到div',dom.style.display)
let
a
=
0
}
Utils
.
showTips
=
function
(
msg
)
{
let
dom
=
document
.
querySelector
(
'#msg_tips'
)
if
(
dom
&&
dom
.
style
.
display
==
'none'
)
{
dom
.
innerText
=
msg
dom
.
style
.
display
=
'block'
setTimeout
(()
=>
{
dom
.
style
.
display
=
'none'
},
2000
);
}
else
{
//console.log('没有找到div',dom.style.display)
let
a
=
0
}
}
Utils
.
teamConfigMap
=
{
joinMode
:
{
'noVerify'
:
'不需要验证'
,
'needVerify'
:
'需要验证'
,
'rejectAll'
:
'禁止任何人加入'
},
beInviteMode
:
{
'needVerify'
:
'需要验证'
,
'noVerify'
:
'不需要验证'
},
inviteMode
:
{
'manager'
:
'管理员邀请'
,
'all'
:
'所有人邀请'
},
updateTeamMode
:
{
'manager'
:
'管理员修改'
,
'all'
:
'所有人修改'
},
memberType
:
{
'manager'
:
'管理员'
,
'normal'
:
'普通成员'
}
joinMode
:
{
'noVerify'
:
'不需要验证'
,
'needVerify'
:
'需要验证'
,
'rejectAll'
:
'禁止任何人加入'
},
beInviteMode
:
{
'needVerify'
:
'需要验证'
,
'noVerify'
:
'不需要验证'
},
inviteMode
:
{
'manager'
:
'管理员邀请'
,
'all'
:
'所有人邀请'
},
updateTeamMode
:
{
'manager'
:
'管理员修改'
,
'all'
:
'所有人修改'
},
memberType
:
{
'manager'
:
'管理员'
,
'normal'
:
'普通成员'
}
}
export
default
Utils
export
default
Utils
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment