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
Show 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,11 +706,15 @@ export default {
}
else
{
this
.
$electron
.
ipcRenderer
.
send
(
"newMessage"
,
[]);
}
try
{
if
(
!
this
.
$electron
.
remote
.
getCurrentWindow
().
isFocused
())
{
if
(
this
.
calcUnRead
(
this
.
newMsgList
,
unreadList
))
{
this
.
$electron
.
remote
.
getCurrentWindow
().
flashFrame
(
true
);
}
}
}
catch
(
error
)
{
}
this
.
newMsgList
=
unreadList
;
...
...
src/store/actions/initNimSDK.js
View file @
665ae7bc
...
...
@@ -6,14 +6,14 @@ 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'
)
...
...
@@ -37,22 +37,22 @@ export function initNimSDK({ state, commit, dispatch }, loginInfo) {
syncSessionUnread
:
true
,
syncRobots
:
true
,
autoMarkRead
:
true
,
// 默认为true
onconnect
:
function
onConnect
(
event
)
{
onconnect
:
function
onConnect
(
event
)
{
if
(
loginInfo
)
{
// 连接上以后更新uid
commit
(
'updateUserUID'
,
loginInfo
)
commit
(
'updateNetError'
,
false
)
commit
(
'updateNetError'
,
false
)
console
.
log
(
'链接完成......'
)
}
},
onerror
:
function
onError
(
event
)
{
commit
(
'updateNetError'
,
true
)
onerror
:
function
onError
(
event
)
{
commit
(
'updateNetError'
,
true
)
},
onwillreconnect
:
function
onWillReconnect
()
{
commit
(
'updateNetError'
,
true
)
commit
(
'updateNetError'
,
true
)
console
.
log
(
'尝试中.........'
)
},
ondisconnect
:
function
onDisconnect
(
error
)
{
ondisconnect
:
function
onDisconnect
(
error
)
{
switch
(
error
.
code
)
{
// 账号或者密码错误, 请跳转到登录页面并提示错误
case
302
:
...
...
@@ -123,12 +123,20 @@ export function initNimSDK({ state, commit, dispatch }, loginInfo) {
onofflinecustomsysmsgs
:
onCustomSysMsgs
,
oncustomsysmsg
:
onCustomSysMsgs
,
// // 同步完成
onsyncdone
:
function
onSyncDone
()
{
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
...
...
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
(){
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
)));
}
}
...
...
@@ -12,37 +15,37 @@ if(!Function.prototype.bind){
let
Utils
=
Object
.
create
(
null
)
Utils
.
encode
=
function
(
_map
,
_content
)
{
Utils
.
encode
=
function
(
_map
,
_content
)
{
_content
=
''
+
_content
if
(
!
_map
||
!
_content
)
{
return
_content
||
''
return
_content
||
''
}
return
_content
.
replace
(
_map
.
r
,
function
(
$1
)
{
return
_content
.
replace
(
_map
.
r
,
function
(
$1
)
{
var
_result
=
_map
[
!
_map
.
i
?
$1
.
toLowerCase
()
:
$1
]
return
_result
!=
null
?
_result
:
$1
});
};
Utils
.
escape
=
(
function
()
{
Utils
.
escape
=
(
function
()
{
let
_reg
=
/<br
\/?
>$/
let
_map
=
{
r
:
/
\<
|
\>
|
\&
|
\r
|
\n
|
\s
|
\'
|
\"
/g
,
'<'
:
'<'
,
'>'
:
'>'
,
'&'
:
'&'
,
' '
:
' '
,
'"'
:
'"'
,
"'"
:
'''
,
'
\
n'
:
'<br/>'
,
'
\
r'
:
''
}
return
function
(
_content
)
{
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
)
{
Utils
.
object2query
=
function
(
obj
)
{
let
keys
=
Object
.
keys
(
obj
)
let
queryArray
=
keys
.
map
(
item
=>
{
return
`
${
item
}
=
${
encodeURIComponent
(
obj
[
item
])}
`
...
...
@@ -50,9 +53,13 @@ Utils.object2query = function (obj) {
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
)
{
Utils
.
mergeObject
=
function
(
dest
,
src
)
{
if
(
typeof
dest
!==
'object'
||
dest
===
null
)
{
dest
=
Object
.
create
(
null
)
}
...
...
@@ -60,7 +67,7 @@ Utils.mergeObject = function (dest, src) {
return
dest
}
Utils
.
mergeVueObject
=
function
(
dest
,
src
)
{
Utils
.
mergeVueObject
=
function
(
dest
,
src
)
{
let
keys
=
Object
.
keys
(
src
)
keys
.
forEach
(
item
=>
{
if
(
typeof
src
[
item
]
!==
'undefined'
)
{
...
...
@@ -71,7 +78,7 @@ Utils.mergeVueObject = function (dest, src) {
}
// 消息类型列表
Utils
.
mapMsgType
=
function
(
msg
)
{
Utils
.
mapMsgType
=
function
(
msg
)
{
let
map
=
{
text
:
'文本消息'
,
image
:
'图片消息'
,
...
...
@@ -88,7 +95,7 @@ Utils.mapMsgType = function (msg) {
return
map
[
type
]
||
'未知消息类型'
}
Utils
.
stringifyDate
=
function
(
datetime
,
simple
=
false
)
{
Utils
.
stringifyDate
=
function
(
datetime
,
simple
=
false
)
{
// let weekMap = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
let
weekMap
=
[
'星期日'
,
'星期一'
,
'星期二'
,
'星期三'
,
'星期四'
,
'星期五'
,
'星期六'
]
datetime
=
new
Date
(
datetime
)
...
...
@@ -128,7 +135,7 @@ Utils.stringifyDate = function (datetime, simple = false) {
}
/* 格式化日期 */
Utils
.
formatDate
=
function
(
datetime
,
simple
=
false
)
{
Utils
.
formatDate
=
function
(
datetime
,
simple
=
false
)
{
let
tempDate
=
(
new
Date
()).
getTime
()
let
result
=
this
.
stringifyDate
(
datetime
,
simple
)
let
thatDay
=
result
.
thatDay
...
...
@@ -147,7 +154,7 @@ Utils.formatDate = function (datetime, simple = false) {
}
}
Utils
.
parseSession
=
function
(
sessionId
)
{
Utils
.
parseSession
=
function
(
sessionId
)
{
if
(
/^p2p-/
.
test
(
sessionId
))
{
return
{
scene
:
'p2p'
,
...
...
@@ -161,7 +168,7 @@ Utils.parseSession = function (sessionId) {
}
}
Utils
.
parseCustomMsg
=
function
(
msg
)
{
Utils
.
parseCustomMsg
=
function
(
msg
)
{
if
(
msg
.
type
===
'custom'
)
{
try
{
let
cnt
=
JSON
.
parse
(
msg
.
content
)
...
...
@@ -179,14 +186,14 @@ Utils.parseCustomMsg = function (msg) {
return
'[自定义消息]'
}
return
''
}
/* 获得有效的备注名 */
Utils
.
getFriendAlias
=
function
(
userInfo
)
{
}
/* 获得有效的备注名 */
Utils
.
getFriendAlias
=
function
(
userInfo
)
{
userInfo
.
alias
=
userInfo
.
alias
?
userInfo
.
alias
.
trim
()
:
''
return
userInfo
.
alias
||
userInfo
.
nick
||
userInfo
.
account
}
Utils
.
generateChatroomSysMsg
=
function
(
data
)
{
Utils
.
generateChatroomSysMsg
=
function
(
data
)
{
let
text
switch
(
data
.
attach
.
type
)
{
case
'memberEnter'
:
...
...
@@ -238,30 +245,34 @@ Utils.generateChatroomSysMsg = function (data) {
return
text
}
Utils
.
generateTeamSysmMsg
=
function
(
data
)
{
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'
:
{
case
'addTeamMembers'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
邀请
${
nicks
.
join
()}
加入群`
break
;
}
case
'removeTeamMembers'
:
{
case
'removeTeamMembers'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
nicks
.
join
()}
被
${
op
}
移出群`
break
;
}
case
'acceptTeamInvite'
:
{
case
'acceptTeamInvite'
:
{
let
op
=
nicks
.
pop
()
text
=
`
${
nicks
.
join
()}
接受了
${
op
}
入群邀请`
break
;
}
case
'passTeamApply'
:
{
case
'passTeamApply'
:
{
let
op
=
nicks
.
shift
()
if
(
nicks
.
length
===
1
&&
op
===
nicks
[
0
])
{
if
(
nicks
.
length
===
1
&&
op
===
nicks
[
0
])
{
// 此情况为高级群设置不需要验证,用户申请入群后,收到的群消息提示
text
=
`
${
op
}
加入群`
}
else
{
...
...
@@ -269,34 +280,40 @@ Utils.generateTeamSysmMsg = function (data) {
}
break
;
}
case
'addTeamManagers'
:
{
case
'addTeamManagers'
:
{
// todo test
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
新增了
${
nicks
}
为管理员`
break
;
}
case
'removeTeamManagers'
:
{
case
'removeTeamManagers'
:
{
// todo test
let
op
=
nicks
.
pop
()
text
=
`
${
op
}
移除了
${
nicks
}
的管理员权限`
break
;
}
case
'leaveTeam'
:
{
case
'leaveTeam'
:
{
text
=
`
${
nicks
.
join
()}
退出了群`
break
;
}
case
'dismissTeam'
:
{
case
'dismissTeam'
:
{
text
=
`
${
nicks
.
join
()}
解散了群`
break
;
}
case
'transferTeam'
:
{
case
'transferTeam'
:
{
// todo test
let
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
let
op
=
nicks
.
shift
()
text
=
`
${
op
}
转让群主给
${
nicks
}
`
break
;
}
case
'updateTeamMute'
:{
case
'updateTeamMute'
:
{
let
nicks
=
this
.
getNickNames
(
data
.
attach
.
users
)
let
op
=
nicks
.
shift
()
text
=
`
${
nicks
}
被管理员
${
data
.
attach
.
mute
?
'禁言'
:
'解除禁言'
}
`
...
...
@@ -311,13 +328,14 @@ Utils.generateTeamSysmMsg = function (data) {
// todo 写成私有成员方法
Utils
.
getNickNames
=
function
(
users
)
{
return
users
.
map
(
user
=>
{
return
user
.
account
===
store
.
state
.
userUID
?
'你'
:
user
.
nick
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
()
let
text
,
team
=
msg
.
attach
.
team
,
op
=
this
.
getNickNames
(
msg
.
attach
.
users
).
pop
()
if
(
team
[
'name'
])
{
text
=
`
${
op
}
修改群名为
${
team
[
'name'
]}
`
}
else
if
(
team
[
'intro'
])
{
...
...
@@ -341,17 +359,17 @@ Utils.getTeamUpdateInfo = function(msg) {
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'
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'
dom
.
style
.
display
=
'none'
},
2000
);
}
else
{
}
else
{
//console.log('没有找到div',dom.style.display)
let
a
=
0
let
a
=
0
}
}
...
...
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