Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xiaomai-cloud-class-web
Overview
Overview
Details
Activity
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
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
xiaomai-cloud-class
xiaomai-cloud-class-web
Commits
b681142d
Commit
b681142d
authored
Jul 02, 2021
by
wufan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat:新建线上课结构调整
parent
f985047c
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
327 additions
and
93 deletions
+327
-93
src/modules/course-manage/video-course/AddVideoCourse.jsx
+205
-72
src/modules/course-manage/video-course/AddVideoCourse.less
+106
-17
src/modules/course-manage/video-course/components/AddVideoIntro.jsx
+16
-4
No files found.
src/modules/course-manage/video-course/AddVideoCourse.jsx
View file @
b681142d
...
...
@@ -8,8 +8,7 @@
*/
import
React
from
'react'
import
{
Button
,
Input
,
Radio
,
message
,
Modal
,
Cascader
}
from
'antd'
import
{
Button
,
Input
,
Radio
,
message
,
Modal
,
Cascader
,
Tooltip
,
Form
,
Popconfirm
}
from
'antd'
import
{
DISK_MAP
,
FileTypeIcon
,
FileVerifyMap
}
from
'@/common/constants/academic/lessonEnum'
import
{
ImgCutModalNew
}
from
'@/components'
import
ShowTips
from
'@/components/ShowTips'
...
...
@@ -27,9 +26,9 @@ import _ from 'underscore'
import
Upload
from
'@/core/upload'
import
{
randomString
}
from
'@/domains/basic-domain/utils'
import
$
from
'jquery'
// import PhotoClip from 'photoclip';
import
'./AddVideoCourse.less'
const
{
TextArea
}
=
Input
;
const
EDIT_BOX_KEY
=
Math
.
random
()
const
fieldNames
=
{
label
:
'categoryName'
,
value
:
'id'
,
children
:
'sonCategoryList'
}
...
...
@@ -58,7 +57,7 @@ class AddVideoCourse extends React.Component {
pageType
,
// 页面类型: add->新建 edit->编辑
imageFile
:
null
,
// 需要被截取的图片
courseName
:
null
,
// 线上课名称
scheduleVideoId
:
null
,
// 线上课链接
scheduleVideoId
s
:
null
,
// 线上课链接
coverId
:
null
,
// 视频封面的recourceId
coverUrl
:
null
,
// 线上课封面
studentList
:
[],
// 上课学员列表
...
...
@@ -83,7 +82,9 @@ class AddVideoCourse extends React.Component {
whetherVisitorsJoin
:
'NO'
,
// 是否允许游客加入
showSelectCoverModal
:
false
,
cutImageBlob
:
null
,
introduce
:
''
introduce
:
''
,
courseChapterList
:[
]
// 课节列表
}
}
...
...
@@ -124,14 +125,14 @@ class AddVideoCourse extends React.Component {
courseId
}).
then
((
res
)
=>
{
const
{
result
=
{}
}
=
res
||
{}
const
{
courseName
,
shelfState
,
whetherVisitorsJoin
,
courseMediaVOS
,
categoryOneName
,
categoryTwoName
,
categoryId
}
=
result
const
{
courseName
,
shelfState
,
whetherVisitorsJoin
,
courseMediaVOS
,
categoryOneName
,
categoryTwoName
,
categoryId
,
courseChapterList
=
[]
}
=
result
let
coverId
let
coverUrl
let
videoType
let
videoDuration
let
videoName
let
scheduleMedia
=
[]
let
scheduleVideoId
let
scheduleVideoId
s
let
scheduleVideoUrl
let
hasIntro
...
...
@@ -144,7 +145,7 @@ class AddVideoCourse extends React.Component {
case
'SCHEDULE'
:
videoDuration
=
item
.
videoDuration
videoName
=
item
.
mediaName
scheduleVideoId
=
item
.
mediaContent
scheduleVideoId
s
=
item
.
mediaContent
scheduleVideoUrl
=
item
.
mediaUrl
videoType
=
item
.
mediaType
break
...
...
@@ -173,12 +174,13 @@ class AddVideoCourse extends React.Component {
videoDuration
,
scheduleMedia
,
courseName
,
scheduleVideoId
,
scheduleVideoId
s
,
scheduleVideoUrl
,
shelfState
,
whetherVisitorsJoin
,
categoryName
,
categoryId
categoryId
,
courseChapterList
})
})
}
...
...
@@ -196,11 +198,11 @@ class AddVideoCourse extends React.Component {
}
handleGoBack
=
()
=>
{
const
{
coverId
,
videoName
,
videoDuration
,
courseName
,
scheduleMedia
,
scheduleVideoId
,
categoryId
,
shelfState
,
whetherVisitorsJoin
}
=
this
.
state
const
{
coverId
,
videoName
,
videoDuration
,
courseName
,
scheduleMedia
,
scheduleVideoId
s
,
categoryId
,
shelfState
,
whetherVisitorsJoin
}
=
this
.
state
if
(
videoName
||
videoDuration
||
scheduleVideoId
||
scheduleVideoId
s
||
!
_
.
isEqual
(
scheduleMedia
,
defaultScheduleMedia
)
||
categoryId
||
courseName
||
...
...
@@ -269,7 +271,7 @@ class AddVideoCourse extends React.Component {
// 显示预览弹窗
handleShowPreviewModal
=
()
=>
{
const
{
coverUrl
,
scheduleVideoUrl
,
courseName
,
scheduleMedia
,
videoDuration
,
introduce
}
=
this
.
state
const
{
coverUrl
,
scheduleVideoUrl
,
courseName
,
scheduleMedia
,
videoDuration
,
introduce
,
courseChapterList
}
=
this
.
state
const
courseBasinInfo
=
{
coverUrl
,
...
...
@@ -292,6 +294,7 @@ class AddVideoCourse extends React.Component {
previewCourseModal
:
null
})
}
}
courseChapterList=
{
courseChapterList
}
/>
)
...
...
@@ -303,20 +306,43 @@ class AddVideoCourse extends React.Component {
this
.
setState
({
showSelectFileModal
:
false
})
const
{
ossUrl
,
resourceId
,
folderName
,
folderFormat
,
folderSize
}
=
file
const
{
ossUrl
,
resourceId
,
folderName
,
folderFormat
,
folderSize
}
=
file
;
let
{
courseChapterList
}
=
this
.
state
;
let
_courseChapterList
=
[...
courseChapterList
];
_courseChapterList
.
push
({
mediaContent
:
resourceId
,
contentType
:
'SCHEDULE'
,
mediaType
:
"VIDEO"
,
mediaName
:
folderName
.
replace
(
'.mp4'
,
''
),
mediaNameAlias
:
folderName
.
replace
(
'.mp4'
,
''
),
videoDuration
:
folderSize
,
id
:
resourceId
,
mediaUrl
:
ossUrl
})
const
videoDom
=
document
.
createElement
(
'video'
)
videoDom
.
src
=
ossUrl
videoDom
.
onloadedmetadata
=
()
=>
{
this
.
setState
({
size
:
folderSize
,
videoName
:
folderName
,
videoType
:
folderFormat
,
scheduleVideoUrl
:
ossUrl
,
scheduleVideoId
:
resourceId
,
videoDuration
:
videoDom
.
duration
})
}
this
.
setState
({
size
:
folderSize
,
videoName
:
folderName
,
videoType
:
folderFormat
,
scheduleVideoUrl
:
ossUrl
,
scheduleVideoIds
:
resourceId
,
videoDuration
:
videoDom
.
duration
,
courseChapterList
:
_courseChapterList
})
// videoDom.src = ossUrl
// videoDom.onloadedmetadata = () => {
// this.setState({
// size: folderSize,
// videoName: folderName,
// videoType: folderFormat,
// scheduleVideoUrl: ossUrl,
// scheduleVideoIds: resourceId,
// videoDuration: videoDom.duration
// })
// }
}
// 保存
...
...
@@ -335,18 +361,19 @@ class AddVideoCourse extends React.Component {
studentList
,
courseName
,
scheduleMedia
,
scheduleVideoId
,
scheduleVideoId
s
,
scheduleVideoUrl
,
categoryId
,
shelfState
,
whetherVisitorsJoin
,
introduce
introduce
,
courseChapterList
}
=
this
.
state
const
commonParams
=
{
videoName
,
videoDuration
,
courseMediaId
:
scheduleVideoId
,
courseMediaId
:
scheduleVideoId
s
,
scheduleMedia
:
scheduleMedia
.
filter
((
item
)
=>
!!
item
.
mediaContent
),
categoryId
,
courseName
,
...
...
@@ -355,10 +382,11 @@ class AddVideoCourse extends React.Component {
storeId
:
User
.
getStoreId
(),
shelfState
,
whetherVisitorsJoin
,
courseType
:
'VOICE'
courseType
:
'VOICE'
,
courseChapterList
}
// 校验必填字段:课程名称, 课程视频
this
.
handleValidate
(
courseName
,
scheduleVideoId
,
categoryId
,
scheduleMedia
).
then
((
res
)
=>
{
this
.
handleValidate
(
courseName
,
scheduleVideoId
s
,
categoryId
,
scheduleMedia
).
then
((
res
)
=>
{
if
(
!
res
)
return
Upload
.
uploadTextToOSS
(
introduce
,
`
${
randomString
()}
.txt`
,
(
introduceId
)
=>
{
this
.
submitRemote
({
id
,
pageType
,
commonParams
:
{
...
commonParams
,
introduceId
}
})
...
...
@@ -390,14 +418,14 @@ class AddVideoCourse extends React.Component {
}
}
handleValidate
=
(
courseName
,
scheduleVideoId
,
categoryId
,
scheduleMedia
)
=>
{
handleValidate
=
(
courseName
,
scheduleVideoId
s
,
categoryId
,
scheduleMedia
)
=>
{
return
new
Promise
((
resolve
)
=>
{
if
(
!
courseName
)
{
message
.
warning
(
'请输入课程名称'
)
resolve
(
false
)
return
false
}
if
(
!
scheduleVideoId
)
{
if
(
!
scheduleVideoId
s
)
{
message
.
warning
(
'请上传视频'
)
resolve
(
false
)
return
false
...
...
@@ -525,11 +553,74 @@ class AddVideoCourse extends React.Component {
}
return
new
Blob
([
ab
],
{
type
:
'image/png'
})
}
handleRenameCourseChapter
=
(
chapterId
,
isFromTextArea
,
value
)
=>
{
let
val
=
value
&&
value
.
trim
();
this
.
handleValidateChapterName
(
val
).
then
(
res
=>
{
// 校验不通过不能点确定保存修改课节名称
if
(
!
res
&&
!
isFromTextArea
)
return
;
let
{
courseChapterList
}
=
this
.
state
;
let
_courseChapterList
=
[];
_courseChapterList
=
courseChapterList
.
map
((
item
,
index
)
=>
{
if
(
item
.
id
===
chapterId
){
// 若是文本框编辑则修改的是视频别名,否则是点击确认修改视频本名
if
(
isFromTextArea
){
item
.
mediaNameAlias
=
val
;
}
else
{
item
.
mediaName
=
item
.
mediaNameAlias
;
}
}
return
item
})
this
.
setState
({
courseChapterList
:
_courseChapterList
})
});
}
handleDeleteCourseChapter
=
(
chapterId
)
=>
{
console
.
log
(
'chapterId---'
,
chapterId
);
let
{
courseChapterList
}
=
this
.
state
;
let
_courseChapterList
=
courseChapterList
.
filter
((
item
,
index
)
=>
{
return
item
.
id
!==
chapterId
})
this
.
setState
({
courseChapterList
:
_courseChapterList
})
}
renderChapterTitle
=
(
item
)
=>
{
const
{
chapterNameValidateStatus
,
chapterNameHelpMsg
}
=
this
.
state
;
return
<
div
className=
"course-chapter-title-popover"
>
<
div
className=
"tag-title"
>
课节名称
</
div
>
<
Form
>
<
Form
.
Item
validateStatus=
{
chapterNameValidateStatus
}
help=
{
chapterNameHelpMsg
}
>
<
TextArea
value=
{
item
.
mediaNameAlias
}
placeholder=
"请输入课节名称"
maxLength=
{
40
}
autoSize
// autoSize={{ minRows: 1, maxRows: 3 }}
style=
{
{
width
:
'318px'
}
}
onChange=
{
(
e
)
=>
{
this
.
handleRenameCourseChapter
(
item
.
id
,
true
,
e
.
target
.
value
)
}
}
/>
</
Form
.
Item
>
</
Form
>
</
div
>
}
render
()
{
const
{
pageType
,
courseName
,
scheduleVideoId
,
scheduleVideoId
s
,
coverId
,
coverUrl
,
scheduleVideoUrl
,
...
...
@@ -552,13 +643,13 @@ class AddVideoCourse extends React.Component {
cutImageBlob
,
introduce
,
loadintroduce
,
id
id
,
courseChapterList
}
=
this
.
state
// 已选择的上课学员数量
const
hasSelectedStu
=
studentList
.
length
const
courseWareIcon
=
FileVerifyMap
[
videoType
]
?
FileTypeIcon
[
FileVerifyMap
[
videoType
].
type
]
:
FileTypeIcon
[
videoType
]
const
defaultCover
=
'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png'
;
const
isDefaultCover
=
coverUrl
===
defaultCover
||
coverUrl
==
null
;
return
(
<
div
className=
'page add-video-course-page'
>
...
...
@@ -586,51 +677,92 @@ class AddVideoCourse extends React.Component {
<
div
className=
'upload-video mt16'
>
<
div
className=
'content flex'
>
<
span
className=
'label required'
>
视频上传:
</
span
>
<
div
className=
'value'
>
{
scheduleVideoId
?
(
<
div
className=
'course-ware'
>
<
img
className=
'course-ware__img'
src=
{
courseWareIcon
}
/>
<
span
className=
'course-ware__name'
>
{
videoName
}
</
span
>
</
div
>
)
:
(
<
div
className=
'course-ware--empty'
>
从资料云盘中选择视频
</
div
>
)
}
</
div
>
</
div
>
<
div
className=
'sub-content'
>
<
Button
onClick=
{
()
=>
{
this
.
setState
({
showSelectFileModal
:
true
})
}
}
>
{
`${pageType === 'add' && !scheduleVideoId ? '选择' : '更换'}视频`
}
</
Button
>
<
span
className=
'tips'
>
视频数量限制1个,大小不超过2G
</
span
>
<
div
className=
"btn-wrap"
>
<
Button
onClick=
{
()
=>
{
if
(
courseChapterList
.
length
>=
20
)
{
message
.
warning
(
`最多只能上传20个文件`
);
return
;
}
this
.
setState
({
showSelectFileModal
:
true
})
}
}
>
{
`${pageType === 'add' && !scheduleVideoIds ? '选择' : '更换'}视频`
}
</
Button
>
<
div
className=
'course-ware--empty'
>
从资料云盘中选择视频
</
div
>
</
div
>
<
div
className=
'tips'
>
视频数量限制20个,每个视频文件大小不超过2G
</
div
>
</
div
>
<
If
condition=
{
courseChapterList
.
length
>
0
}
>
<
div
className=
"course-chapter-list-wrap"
>
<
div
className=
"course-chapter-total"
>
{
`共${courseChapterList.length}个课节`
}
</
div
>
<
div
className=
'course-chapter-list'
id=
"course-chapter-list"
>
{
_
.
map
(
courseChapterList
,(
item
,
index
)
=>
{
return
<
div
className=
'course-ware'
key=
{
index
}
>
<
div
>
{
`0${index + 1 } `
}
</
div
>
<
img
className=
'course-ware__img'
src=
{
courseWareIcon
}
alt=
''
/>
<
div
className=
'course-ware__name'
>
{
item
.
mediaName
&&
item
.
mediaName
.
length
>
24
?
<
Tooltip
title=
{
item
.
mediaName
}
>
{
item
.
mediaName
}
</
Tooltip
>:
item
.
mediaName
}
</
div
>
<
div
className=
"course-chapter__opt"
id=
{
item
.
id
}
>
<
Popconfirm
placement=
"topLeft"
className=
"course-chapter-tooltip"
title=
{
this
.
renderChapterTitle
(
item
)
}
color=
'#fff'
trigger=
"click"
overlayClassName=
"chapter-popover"
getPopupContainer=
{
()
=>
document
.
getElementById
(
'course-chapter-list'
)
}
onConfirm=
{
()
=>
this
.
handleRenameCourseChapter
(
item
.
id
,
false
,
item
.
mediaNameAlias
)
}
icon=
{
null
}
maskClosable=
{
false
}
>
<
div
className=
"rename"
>
重命名
</
div
>
</
Popconfirm
>
<
div
className=
"line"
>
|
</
div
>
<
div
className=
"delete"
onClick=
{
()
=>
this
.
handleDeleteCourseChapter
(
item
.
id
)
}
>
移除
</
div
>
</
div
>
</
div
>
})
}
</
div
>
</
div
>
</
If
>
</
div
>
<
div
className=
'cover-url flex mt16'
>
<
div
className=
'label'
>
视频封面:
</
div
>
<
div
className=
'label'
>
课程封面:
</
div
>
<
div
className=
'cover-url__wrap'
>
<
div
className=
'img-content'
>
{
/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */
}
{
scheduleVideoUrl
||
coverUrl
?
(
<
img
src=
{
coverUrl
||
`${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`
}
/>
)
:
(
<
div
className=
'empty-img'
>
若不上传
<
br
/>
系统默认将视频首帧作为封面图
</
div
>
)
}
</
div
>
<
div
className=
'opt-btns'
>
<
Button
onClick=
{
()
=>
{
this
.
setState
({
showSelectCoverModal
:
true
})
}
}
>
{
`${pageType === 'add' && !scheduleVideoId && !coverUrl ? '上传' : '修改'}封面`
}
</
Button
>
<
div
>
<
Button
onClick=
{
()
=>
{
this
.
setState
({
showSelectCoverModal
:
true
})
}
}
>
{
`${pageType === 'add' && !coverUrl ? '上传' : '修改'}封面`
}
</
Button
>
<
span
className=
{
`span ${coverUrl ? 'defalut-span' : ''}`
}
onClick=
{
()
=>
{
this
.
setState
({
coverUrl
:
''
,
coverId
:
''
})
}
}
>
使用默认图
</
span
>
</
div
>
<
div
className=
'tips'
>
建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。
</
div
>
</
div
>
<
div
className=
'img-content'
>
{
isDefaultCover
&&
<
span
className=
"tag"
>
默认图
</
span
>
}
{
/* 如果视频和封面都没有上传的话, 那么就显示缺省, 如果上传了视频, 那么封面图就默认显示视频的第一帧, 如果上传了封面图, 那么就显示上传的封面图 */
}
<
img
src=
{
coverUrl
||
`https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png`
}
alt=
''
/>
</
div
>
</
div
>
</
div
>
<
div
className=
'course-catalog required'
>
...
...
@@ -695,6 +827,7 @@ class AddVideoCourse extends React.Component {
{
/* 选择备课文件弹窗 */
}
{
showSelectFileModal
&&
(
<
SelectPrepareFileModal
multiple=
{
true
}
operateType=
'select'
selectTypeList=
{
[
'MP4'
]
}
accept=
'video/mp4'
...
...
src/modules/course-manage/video-course/AddVideoCourse.less
View file @
b681142d
...
...
@@ -49,8 +49,8 @@
}
}
.course-catalog{
margin-bottom:
16
px;
margin-top:
16
px;
margin-bottom:
24
px;
margin-top:
24
px;
}
.course-ware {
display: flex;
...
...
@@ -75,7 +75,19 @@
.img-content {
width: 298px;
height: 172px;
position: relative;
.tag {
border-radius: 2px;
background: rgba(51, 51, 51, .2);
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
img {
width: 100%;
height: 100%;
...
...
@@ -85,21 +97,26 @@
.empty-img {
width: 298px;
height: 172px;
border: 1px dashed #
EBEBEB
;
border: 1px dashed #
ebebeb
;
border-radius: 4px;
padding: 12px;
color: #999;
padding: 52px 24px;
text-align: center;
}
.opt-btns {
margin-top: 8px;
display: flex;
align-items: center;
margin-bottom: 8px;
.span {
color: #ccc;
margin-left: 8px;
}
.defalut-span {
cursor: pointer;
color: #2966ff;
}
.tips {
margin-
left: 12
px;
margin-
top: 8
px;
color: #999;
}
}
...
...
@@ -115,14 +132,86 @@
color: #333;
}
}
.upload-video {
display: flex;
margin-bottom: 24px;
.sub-content {
margin-top: 4px;
.btn-wrap {
display: flex;
.course-ware--empty {
margin-left: 12px;
line-height: 28px;
}
}
.tips {
margin-top: 8px;
color: #999;
}
}
}
.sub-content {
margin-left: 85px;
margin-top: 4px;
.tips {
margin-left: 4px;
color: #999;
.course-chapter-list-wrap {
position: relative;
.course-chapter-total {
height: 40px;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
margin-bottom: 18px;
position: absolute;
top: 1px;
left: 1px;
z-index: 10;
background: #fff;
width: 550px;
text-indent: 16px;
line-height: 40px;
}
.course-chapter-list {
margin-top: 8px;
width: 552px;
max-height: 247px;
min-height: 130px;
overflow-y: auto ;
border-radius: 4px;
border: 1px solid #E8E8E8;
padding: 50px 16px 20px 16px;
.course-ware {
display: flex;
align-items: center;
margin-bottom: 20px;
color: #666;
&:last-child {
margin-bottom: 0;
}
&__img {
width: 24px;
margin-right: 4px;
margin-left: 16px;
}
&__name {
color: #333;
width: 356px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.course-chapter__opt {
display: flex;
color: #2966FF;
.line {
color: #BFBFBF;
margin-left: 4px;
margin-right: 4px;
}
.delete, .rename {
cursor: pointer;
}
}
}
}
}
}
...
...
src/modules/course-manage/video-course/components/AddVideoIntro.jsx
View file @
b681142d
...
...
@@ -121,8 +121,14 @@ class AddVideoIntro extends React.Component {
</
div
>
<
div
>
<
div
className=
"desc"
>
<
div
>
开启:允许未绑定手机号的学员观看
</
div
>
<
div
>
关闭:仅限绑定了手机号的学员可以进入观看视频
</
div
>
<
Choose
>
<
When
condition=
{
whetherVisitorsJoin
===
"YES"
}
>
<
div
>
已开启,学员需绑定手机号才可观看
</
div
>
</
When
>
<
Otherwise
>
<
div
>
已关闭,学员无需绑定手机号即可观看
</
div
>
</
Otherwise
>
</
Choose
>
</
div
>
</
div
>
</
div
>
...
...
@@ -136,8 +142,14 @@ class AddVideoIntro extends React.Component {
</
Col
>
<
Col
span=
{
21
}
>
<
div
className=
"desc"
>
<
div
>
开启:此视频将在学员学院的视频列表中出现
</
div
>
<
div
>
关闭:此视频将在学员学院的视频列表中隐藏
</
div
>
<
Choose
>
<
When
condition=
{
shelfState
===
"YES"
}
>
<
div
>
已开启,课程将在该学院的学员课程列表中显示
</
div
>
</
When
>
<
Otherwise
>
<
div
>
已关闭,课程将在该学院的学员课程列表中隐藏
</
div
>
</
Otherwise
>
</
Choose
>
</
div
>
</
Col
>
</
Row
>
...
...
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