Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
lib
react-ag-qeditor
Commits
5499f2c8
Commit
5499f2c8
authored
Oct 02, 2023
by
Nikita
Browse files
added remove iframe button
parent
6ce5f728
Changes
6
Hide whitespace changes
Inline
Side-by-side
src/QEditor.jsx
View file @
5499f2c8
...
@@ -31,6 +31,9 @@ import ReactStopwatch from 'react-stopwatch';
...
@@ -31,6 +31,9 @@ import ReactStopwatch from 'react-stopwatch';
import
Audio
from
"
./extensions/Audio
"
;
import
Audio
from
"
./extensions/Audio
"
;
import
{
isMobile
}
from
'
react-device-detect
'
;
import
{
isMobile
}
from
'
react-device-detect
'
;
import
IframeModal
from
"
./modals/IframeModal
"
;
import
IframeCustomModal
from
"
./modals/IframeCustomModal
"
;
import
RemoveIframeModal
from
"
./modals/RemoveIframeModal
"
;
const
initialBubbleItems
=
[
'
bold
'
,
'
italic
'
,
'
underline
'
,
'
strike
'
,
'
|
'
,
'
colorText
'
,
'
highlight
'
];
const
initialBubbleItems
=
[
'
bold
'
,
'
italic
'
,
'
underline
'
,
'
strike
'
,
'
|
'
,
'
colorText
'
,
'
highlight
'
];
...
@@ -55,6 +58,7 @@ const QEditor = ({
...
@@ -55,6 +58,7 @@ const QEditor = ({
const
[
oldFocusFromTo
,
setOldFocusFromTo
]
=
useState
(
null
);
const
[
oldFocusFromTo
,
setOldFocusFromTo
]
=
useState
(
null
);
const
[
isUploading
,
setIsUploading
]
=
useState
(
false
);
const
[
isUploading
,
setIsUploading
]
=
useState
(
false
);
const
[
recordType
,
setRecordType
]
=
useState
({
video
:
true
})
const
[
recordType
,
setRecordType
]
=
useState
({
video
:
true
})
const
[
currentRemoveIframe
,
setCurrentRemoveIframe
]
=
useState
(
null
);
const
getRgb
=
(
hex
)
=>
{
const
getRgb
=
(
hex
)
=>
{
var
result
=
/^#
?([
a-f
\d]{2})([
a-f
\d]{2})([
a-f
\d]{2})
$/i
.
exec
(
hex
);
var
result
=
/^#
?([
a-f
\d]{2})([
a-f
\d]{2})([
a-f
\d]{2})
$/i
.
exec
(
hex
);
...
@@ -495,29 +499,18 @@ const QEditor = ({
...
@@ -495,29 +499,18 @@ const QEditor = ({
const
getInnerModal
=
()
=>
{
const
getInnerModal
=
()
=>
{
switch
(
innerModalType
)
{
switch
(
innerModalType
)
{
case
'
remove_iframe
'
:
return
<
RemoveIframeModal
/>
case
'
iframe
'
:
case
'
iframe
'
:
return
(
return
<
IframeModal
<
Fragment
>
embedContent
=
{
embedContent
}
<
input
type
=
"text"
value
=
{
embedContent
}
placeholder
=
{
'
https://
'
}
setEmbedContent
=
{
setEmbedContent
}
onInput
=
{
(
e
)
=>
setEmbedContent
(
e
.
target
.
value
)
/>
}
/>
<
ul
className
=
{
'
atma-editor-soc-video
'
}
>
<
li
className
=
{
'
youtube
'
}
/>
<
li
className
=
{
'
vimeo
'
}
/>
{
/* <li className={'vk'}/> */
}
<
li
className
=
{
'
ok
'
}
/>
<
li
className
=
{
'
rutube
'
}
/>
</
ul
>
</
Fragment
>
)
case
'
iframe_custom
'
:
case
'
iframe_custom
'
:
return
(
return
<
IframeCustomModal
<
Fragment
>
embedContent
=
{
embedContent
}
<
textarea
style
=
{
{
width
:
'
100%
'
,
height
:
'
100%
'
}
}
rows
=
{
18
}
value
=
{
embedContent
}
placeholder
=
{
'
<iframe></iframe>
'
}
setEmbedContent
=
{
setEmbedContent
}
onInput
=
{
(
e
)
=>
setEmbedContent
(
e
.
target
.
value
)
}
/>
/>
</
Fragment
>
)
case
'
iframe_pptx
'
:
case
'
iframe_pptx
'
:
return
(
return
(
<
Fragment
>
{
getUploader
({
accept
:
'
application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.slideshow, application/vnd.openxmlformats-officedocument.presentationml.presentation
'
,
afterParams
:
[
'
no_convert=1
'
]})
}
</
Fragment
>
<
Fragment
>
{
getUploader
({
accept
:
'
application/vnd.ms-powerpoint, application/vnd.openxmlformats-officedocument.presentationml.slideshow, application/vnd.openxmlformats-officedocument.presentationml.presentation
'
,
afterParams
:
[
'
no_convert=1
'
]})
}
</
Fragment
>
...
@@ -536,7 +529,6 @@ const QEditor = ({
...
@@ -536,7 +529,6 @@ const QEditor = ({
)
)
case
'
voicemessage
'
:
case
'
voicemessage
'
:
return
(
return
(
<>
<
Fragment
>
<
Fragment
>
{
{
isMobile
&&
isMobile
&&
...
@@ -577,7 +569,6 @@ const QEditor = ({
...
@@ -577,7 +569,6 @@ const QEditor = ({
</
div
>
</
div
>
}
}
</
Fragment
>
</
Fragment
>
</>
)
)
case
'
screencust
'
:
case
'
screencust
'
:
return
(
return
(
...
@@ -794,6 +785,181 @@ const QEditor = ({
...
@@ -794,6 +785,181 @@ const QEditor = ({
return
null
return
null
}
}
const
buttons
=
innerModalType
===
'
remove_iframe
'
?
[
{
title
:
'
Отмена
'
,
className
:
'
atma-editor-cancel
'
,
onClick
:
()
=>
{
stopRecording
();
unMuteAudio
();
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setUploadedPaths
([]);
setModalIsOpen
(
false
);
}
},
{
title
:
'
Удалить
'
,
className
:
'
atma-editor-complete
'
,
onClick
:
()
=>
{
currentRemoveIframe
?.
remove
();
stopRecording
();
unMuteAudio
();
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setUploadedPaths
([]);
setModalIsOpen
(
false
);
}
},
]
:
[
{
title
:
'
Отмена
'
,
className
:
'
atma-editor-cancel
'
,
onClick
:
()
=>
{
stopRecording
();
unMuteAudio
();
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setUploadedPaths
([]);
setModalIsOpen
(
false
);
}
},
{
title
:
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
?
(
isUploading
?
'
Сохранение...
'
:
'
Вставить
'
)
:
'
Вставить
'
,
className
:
'
atma-editor-complete
'
,
onClick
:
async
()
=>
{
if
((
status
===
'
recording
'
||
isUploading
))
{
return
false
;
}
else
{
if
(
document
.
querySelectorAll
(
'
.atma-editor-uploader-progress
'
).
length
>
0
)
{
if
(
!
confirm
(
'
Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?
'
))
{
return
false
;
}
}
try
{
switch
(
innerModalType
)
{
case
'
image
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
editor
.
chain
().
focus
().
setImage
({
src
:
file
.
path
}).
run
();
});
break
case
'
video
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
editor
.
chain
().
focus
().
setVideo
({
src
:
file
.
path
,
poster
:
file
.
path
+
'
.jpg
'
}).
run
();
});
break
case
'
voicemessage
'
:
if
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
{
if
(
!
isUploading
)
{
await
saveScreenCust
(
mediaBlobUrl
).
then
(
data
=>
{
if
(
data
?.
file_path
)
{
editor
.
chain
().
focus
().
addVoiceMessage
({
src
:
data
.
file_path
}).
run
();
}
});
}
}
break
case
'
screencust
'
:
if
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
{
if
(
!
isUploading
)
{
await
saveScreenCust
(
mediaBlobUrl
).
then
(
data
=>
{
if
(
data
?.
file_path
)
{
editor
.
chain
().
focus
().
setVideo
({
src
:
data
.
file_path
}).
run
();
}
});
}
}
break
case
'
webcamera
'
:
if
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
{
if
(
!
isUploading
)
{
await
saveScreenCust
(
mediaBlobUrl
).
then
(
data
=>
{
if
(
data
?.
file_path
)
{
editor
.
chain
().
focus
().
setVideo
({
src
:
data
.
file_path
}).
run
();
}
});
}
}
break
case
'
iframe
'
:
let
_url
=
embedContent
;
let
reg
=
/
(
http|https
)
:
\/\/([\w
.
]
+
\/?)\S
*/
;
const
url
=
new
URL
(
reg
.
test
(
_url
)
?
_url
:
'
https:
'
+
_url
);
let
urlId
=
url
.
pathname
.
replace
(
/
\/
$/ig
,
''
).
split
(
'
/
'
).
pop
();
switch
(
url
.
hostname
)
{
case
'
rutube.ru
'
:
case
'
www.rutube.ru
'
:
_url
=
`https://rutube.ru/pl/?pl_id&pl_type&pl_video=
${
urlId
}
`
;
break
case
'
vimeo.com
'
:
_url
=
`https://player.vimeo.com/video/
${
urlId
}
`
;
break
case
'
ok.ru
'
:
case
'
www.ok.ru
'
:
_url
=
`//ok.ru/videoembed/
${
urlId
}
`
;
break
case
'
youtu.be
'
:
case
'
youtube.com
'
:
case
'
www.youtube.com
'
:
if
(
url
.
hostname
.
indexOf
(
'
youtu.be
'
)
===
-
1
&&
url
.
search
!==
''
)
{
if
(
url
.
searchParams
.
get
(
'
v
'
))
{
urlId
=
url
.
searchParams
.
get
(
'
v
'
);
}
}
_url
=
`https://www.youtube.com/embed/
${
urlId
}
`
;
break
}
editor
.
chain
().
focus
().
setIframe
({
src
:
_url
,
setModalIsOpen
,
setInnerModalType
,
setModalTitle
,
setCurrentRemoveIframe
}).
run
();
break
case
'
iframe_custom
'
:
editor
.
chain
().
focus
().
insertContent
(
embedContent
).
run
();
break
case
'
iframe_pptx
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
editor
.
chain
().
focus
().
insertContent
(
`<iframe src="https://view.officeapps.live.com/op/embed.aspx?src=
${
file
.
path
}
" width="100%" height="600px" frameBorder="0"></iframe>`
).
run
();
})
break
case
'
file
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
let
exp
=
file
.
path
.
split
(
'
.
'
);
exp
=
exp
[
exp
.
length
-
1
]
editor
.
chain
().
focus
().
insertContent
(
`<a href="
${
file
.
path
}
" target="_blank" download="
${
file
.
name
}
.
${
exp
}
" data-size="
${
file
.
size
}
">
${
file
.
name
}
</a>`
).
run
();
});
break
}
setModalIsOpen
(
false
);
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setEmbedContent
(
''
);
setUploadedPaths
([]);
setModalTitle
(
''
);
}
catch
(
err
)
{
console
.
log
(
err
);
setModalIsOpen
(
false
);
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setEmbedContent
(
''
);
setUploadedPaths
([]);
setModalTitle
(
''
);
}
}
},
disabled
:
isDisabledAction
()
}
];
return
(
return
(
<
div
<
div
className
=
"atma-editor-wrap"
className
=
"atma-editor-wrap"
...
@@ -873,152 +1039,7 @@ const QEditor = ({
...
@@ -873,152 +1039,7 @@ const QEditor = ({
getInnerModal
()
getInnerModal
()
}
}
{
{
buildActionsModal
([
buildActionsModal
(
buttons
)
{
title
:
'
Отмена
'
,
className
:
'
atma-editor-cancel
'
,
onClick
:
()
=>
{
stopRecording
();
unMuteAudio
();
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setUploadedPaths
([]);
setModalIsOpen
(
false
);
}
},
{
title
:
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
?
(
isUploading
?
'
Сохранение...
'
:
'
Вставить
'
)
:
'
Вставить
'
,
className
:
'
atma-editor-complete
'
,
onClick
:
async
()
=>
{
if
((
status
===
'
recording
'
||
isUploading
))
{
return
false
;
}
else
{
if
(
document
.
querySelectorAll
(
'
.atma-editor-uploader-progress
'
).
length
>
0
)
{
if
(
!
confirm
(
'
Не полностью загруженные файлы будут утеряны. Вы уверены, что хотите продолжить?
'
))
{
return
false
;
}
}
try
{
switch
(
innerModalType
)
{
case
'
image
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
editor
.
chain
().
focus
().
setImage
({
src
:
file
.
path
}).
run
();
});
break
case
'
video
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
editor
.
chain
().
focus
().
setVideo
({
src
:
file
.
path
,
poster
:
file
.
path
+
'
.jpg
'
}).
run
();
});
break
case
'
voicemessage
'
:
if
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
{
if
(
!
isUploading
)
{
await
saveScreenCust
(
mediaBlobUrl
).
then
(
data
=>
{
if
(
data
?.
file_path
)
{
editor
.
chain
().
focus
().
addVoiceMessage
({
src
:
data
.
file_path
}).
run
();
}
});
}
}
break
case
'
screencust
'
:
if
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
{
if
(
!
isUploading
)
{
await
saveScreenCust
(
mediaBlobUrl
).
then
(
data
=>
{
if
(
data
?.
file_path
)
{
editor
.
chain
().
focus
().
setVideo
({
src
:
data
.
file_path
}).
run
();
}
});
}
}
break
case
'
webcamera
'
:
if
(
mediaBlobUrl
&&
uploadedPaths
.
length
===
0
)
{
if
(
!
isUploading
)
{
await
saveScreenCust
(
mediaBlobUrl
).
then
(
data
=>
{
if
(
data
?.
file_path
)
{
editor
.
chain
().
focus
().
setVideo
({
src
:
data
.
file_path
}).
run
();
}
});
}
}
break
case
'
iframe
'
:
let
_url
=
embedContent
;
let
reg
=
/
(
http|https
)
:
\/\/([\w
.
]
+
\/?)\S
*/
;
const
url
=
new
URL
(
reg
.
test
(
_url
)
?
_url
:
'
https:
'
+
_url
);
let
urlId
=
url
.
pathname
.
replace
(
/
\/
$/ig
,
''
).
split
(
'
/
'
).
pop
();
switch
(
url
.
hostname
)
{
case
'
rutube.ru
'
:
case
'
www.rutube.ru
'
:
_url
=
`https://rutube.ru/pl/?pl_id&pl_type&pl_video=
${
urlId
}
`
;
break
case
'
vimeo.com
'
:
_url
=
`https://player.vimeo.com/video/
${
urlId
}
`
;
break
case
'
ok.ru
'
:
case
'
www.ok.ru
'
:
_url
=
`//ok.ru/videoembed/
${
urlId
}
`
;
break
case
'
youtu.be
'
:
case
'
youtube.com
'
:
case
'
www.youtube.com
'
:
if
(
url
.
hostname
.
indexOf
(
'
youtu.be
'
)
===
-
1
&&
url
.
search
!==
''
)
{
if
(
url
.
searchParams
.
get
(
'
v
'
))
{
urlId
=
url
.
searchParams
.
get
(
'
v
'
);
}
}
_url
=
`https://www.youtube.com/embed/
${
urlId
}
`
;
break
}
editor
.
chain
().
focus
().
setIframe
({
src
:
_url
}).
run
();
break
case
'
iframe_custom
'
:
editor
.
chain
().
focus
().
insertContent
(
embedContent
).
run
();
break
case
'
iframe_pptx
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
editor
.
chain
().
focus
().
insertContent
(
`<iframe src="https://view.officeapps.live.com/op/embed.aspx?src=
${
file
.
path
}
" width="100%" height="600px" frameBorder="0"></iframe>`
).
run
();
})
break
case
'
file
'
:
uploadedPaths
.
map
((
file
,
i
)
=>
{
let
exp
=
file
.
path
.
split
(
'
.
'
);
exp
=
exp
[
exp
.
length
-
1
]
editor
.
chain
().
focus
().
insertContent
(
`<a href="
${
file
.
path
}
" target="_blank" download="
${
file
.
name
}
.
${
exp
}
" data-size="
${
file
.
size
}
">
${
file
.
name
}
</a>`
).
run
();
});
break
}
setModalIsOpen
(
false
);
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setEmbedContent
(
''
);
setUploadedPaths
([]);
setModalTitle
(
''
);
}
catch
(
err
)
{
console
.
log
(
err
);
setModalIsOpen
(
false
);
clearBlobUrl
();
setUploaderUid
(
`uid
${
new
Date
()}
`
);
setEmbedContent
(
''
);
setUploadedPaths
([]);
setModalTitle
(
''
);
}
}
},
disabled
:
isDisabledAction
()
}
])
}
}
</
EditorModal
>
</
EditorModal
>
</
div
>
</
div
>
...
...
src/extensions/Iframe.js
View file @
5499f2c8
...
@@ -21,6 +21,18 @@ const Iframe = Node.create({
...
@@ -21,6 +21,18 @@ const Iframe = Node.create({
console
.
log
(
this
)
console
.
log
(
this
)
},
},
},
},
"
setInnerModalType
"
:
{
default
:
null
},
"
setModalIsOpen
"
:
{
default
:
null
},
"
setModalTitle
"
:
{
default
:
null
},
"
setCurrentRemoveIframe
"
:
{
default
:
null
}
}
}
},
},
...
@@ -38,21 +50,35 @@ const Iframe = Node.create({
...
@@ -38,21 +50,35 @@ const Iframe = Node.create({
addNodeView
()
{
addNodeView
()
{
return
({
editor
,
node
,
...
a
})
=>
{
return
({
editor
,
node
,
...
a
})
=>
{
const
container
=
document
.
createElement
(
'
div
'
);
// div.className = 'aspect-w-16 aspect-h-9' + (editor.isEditable ? ' cursor-pointer' : '');
const
iframe
=
document
.
createElement
(
'
iframe
'
);
const
iframe
=
document
.
createElement
(
'
iframe
'
);
if
(
editor
.
isEditable
)
{
iframe
.
className
=
'
pointer-events-none
'
;
}
iframe
.
src
=
node
.
attrs
.
src
;
iframe
.
src
=
node
.
attrs
.
src
;
iframe
.
frameBorder
=
node
.
attrs
.
frameborder
;
iframe
.
allowfullscreen
=
node
.
attrs
.
allowfullscreen
;
iframe
.
allowfullscreen
=
node
.
attrs
.
allowfullscreen
;
iframe
.
style
=
'
width:1280px;height:auto;aspect-ratio: 16 / 9;
'
;
iframe
.
classList
.
add
(
'
customIframe
'
);
// div.append(video);
const
closeBtn
=
document
.
createElement
(
'
button
'
);
closeBtn
.
textContent
=
'
X
'
;
closeBtn
.
classList
.
add
(
'
closeBtn
'
);
closeBtn
.
addEventListener
(
'
click
'
,
function
()
{
try
{
node
.
attrs
.
setModalTitle
(
'
Вы уверены, что хотите удалить?
'
);
node
.
attrs
.
setInnerModalType
(
'
remove_iframe
'
);
node
.
attrs
.
setModalIsOpen
(
true
);
node
.
attrs
.
setCurrentRemoveIframe
(
container
);
}
catch
{
container
.
remove
();
}
});
// if (editor.isEditable) {
// container.classList.add('pointer-events-none');
// }
container
.
append
(
closeBtn
,
iframe
);
return
{
return
{
dom
:
iframe
,
dom
:
container
,
}
}
}
}
},
},
...
@@ -62,7 +88,7 @@ const Iframe = Node.create({
...
@@ -62,7 +88,7 @@ const Iframe = Node.create({
setIframe
:
(
options
)
=>
({
tr
,
dispatch
})
=>
{
setIframe
:
(
options
)
=>
({
tr
,
dispatch
})
=>
{
const
{
selection
}
=
tr
const
{
selection
}
=
tr
const
node
=
this
.
type
.
create
(
options
)
const
node
=
this
.
type
.
create
(
options
)
//
if
(
dispatch
)
{
if
(
dispatch
)
{
tr
.
replaceRangeWith
(
selection
.
from
,
selection
.
to
,
node
)
tr
.
replaceRangeWith
(
selection
.
from
,
selection
.
to
,
node
)
}
}
...
...
src/index.scss
View file @
5499f2c8
...
@@ -1022,4 +1022,25 @@ body{
...
@@ -1022,4 +1022,25 @@ body{
.qseparator{
.qseparator{
width: 16px;
width: 16px;
}
}
.closeBtn {
position: relative;
display: flex;
justify-content: end;
border-radius: 50%;
border: none;
background-color: #2677e3;
color: #fff;
font-size: 0.5rem;
padding: 4px 6px;
top: 10px;
cursor: pointer;
right: 8px;
}
.customIframe {
width:1280px;
height:auto;
aspect-ratio: 16 / 9;
}
}
}
src/modals/IframeCustomModal.js
0 → 100644
View file @
5499f2c8
import
React
,
{
Fragment
}
from
"
react
"
;
export
default
function
IframeCustomModal
({
embedContent
,
setEmbedContent
})
{
return
(
<
Fragment
>
<
textarea
style
=
{{
width
:
'
100%
'
,
height
:
'
100%
'
}}
rows
=
{
18
}
value
=
{
embedContent
}
placeholder
=
{
'
<iframe></iframe>
'
}
onInput
=
{(
e
)
=>
setEmbedContent
(
e
.
target
.
value
)}
/
>
<
/Fragment
>
)
}
src/modals/IframeModal.js
0 → 100644
View file @
5499f2c8
import
React
,
{
Fragment
}
from
"
react
"
;
export
default
function
IframeModal
({
embedContent
,
setEmbedContent
})
{
return
(
<
Fragment
>
<
input
type
=
"
text
"
value
=
{
embedContent
}
placeholder
=
{
'
https://
'
}
onInput
=
{(
e
)
=>
setEmbedContent
(
e
.
target
.
value
)
}
/
>
<
ul
className
=
{
'
atma-editor-soc-video
'
}
>
<
li
className
=
{
'
youtube
'
}
/
>
<
li
className
=
{
'
vimeo
'
}
/
>
{
/* <li className={'vk'}/> */
}
<
li
className
=
{
'
ok
'
}
/
>
<
li
className
=
{
'
rutube
'
}
/
>
<
/ul
>
<
/Fragment
>
)
}
src/modals/RemoveIframeModal.js
0 → 100644
View file @
5499f2c8
import
React
,
{
Fragment
}
from
"
react
"
;
export
default
function
RemoveIframeModal
(){
return
(
<
Fragment
>
<
/Fragment
>
)
}
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