Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
4855051d
Commit
4855051d
authored
Jun 20, 2020
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: page published state + comments localization
parent
83b83a75
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
105 additions
and
70 deletions
+105
-70
comments.vue
client/components/comments.vue
+21
-19
editor.vue
client/components/editor.vue
+10
-0
editor-modal-properties.vue
client/components/editor/editor-modal-properties.vue
+4
-4
page.vue
client/themes/default/components/page.vue
+4
-2
common.js
server/controllers/common.js
+34
-45
auth.js
server/core/auth.js
+30
-0
editor.pug
server/views/editor.pug
+2
-0
No files found.
client/components/comments.vue
View file @
4855051d
...
...
@@ -3,7 +3,7 @@
v-textarea#discussion-new(
outlined
flat
placeholder='Write a new comment...
'
:placeholder='$t(`common:comments.newPlaceholder`)
'
auto-grow
dense
rows='3'
...
...
@@ -19,7 +19,7 @@
outlined
color='blue-grey darken-2'
:background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
placeholder='Your Name
'
:placeholder='$t(`common:comments.fieldName`)
'
hide-details
dense
autocomplete='name'
...
...
@@ -30,7 +30,7 @@
outlined
color='blue-grey darken-2'
:background-color='$vuetify.theme.dark ? `grey darken-5` : `white`'
placeholder='Your Email Address
'
:placeholder='$t(`common:comments.fieldEmail`)
'
hide-details
type='email'
dense
...
...
@@ -39,9 +39,11 @@
)
.d-flex.align-center.pt-3(v-if='permissions.write')
v-icon.mr-1(color='blue-grey') mdi-language-markdown-outline
.caption.blue-grey--text
Markdown Format
.caption.blue-grey--text
{{
$t
(
'common:comments.markdownFormat'
)
}}
v-spacer
.caption.mr-3(v-if='isAuthenticated') Posting as #[strong
{{
userDisplayName
}}
]
.caption.mr-3(v-if='isAuthenticated')
i18next(tag='span', path='common:comments.postingAs')
strong(place='bold')
{{
userDisplayName
}}
v-btn(
dark
color='blue-grey darken-2'
...
...
@@ -49,7 +51,7 @@
depressed
)
v-icon(left) mdi-comment
span.text-none
Post Comment
span.text-none
{{
$t
(
'common:comments.postComment'
)
}}
v-divider.mt-3(v-if='permissions.write')
.pa-5.d-flex.align-center.justify-center(v-if='isLoading && !hasLoadedOnce')
v-progress-circular(
...
...
@@ -58,7 +60,7 @@
width='1'
color='blue-grey'
)
.caption.blue-grey--text.pl-3: em
Loading comments...
.caption.blue-grey--text.pl-3: em
{{
$t
(
'common:comments.loading'
)
}}
v-timeline(
dense
v-else-if='comments && comments.length > 0'
...
...
@@ -80,7 +82,7 @@
v-icon.mr-3(small, @click='editComment(cm)') mdi-pencil
v-icon(small, @click='deleteCommentConfirm(cm)') mdi-delete
.comments-post-name.caption: strong
{{
cm
.
authorName
}}
.comments-post-date.overline.grey--text
{{
cm
.
createdAt
|
moment
(
'from'
)
}}
#[em(v-if='cm.createdAt !== cm.updatedAt') -
modified
{{
cm
.
updatedAt
|
moment
(
'from'
)
}}
]
.comments-post-date.overline.grey--text
{{
cm
.
createdAt
|
moment
(
'from'
)
}}
#[em(v-if='cm.createdAt !== cm.updatedAt') -
{{
$t
(
'common:comments.modified'
,
{
reldate
:
$options
.
filters
.
moment
(
cm
.
updatedAt
,
'from'
)
}
)
}}
]
.
comments
-
post
-
content
.
mt
-
3
(
v
-
if
=
'commentEditId !== cm.id'
,
v
-
html
=
'cm.render'
)
.
comments
-
post
-
editcontent
.
mt
-
3
(
v
-
else
)
v
-
textarea
(
...
...
@@ -103,7 +105,7 @@
outlined
)
v
-
icon
(
left
)
mdi
-
close
span.text-none
Cancel
span
.
text
-
none
{{
$t
(
'common:action.cancel'
)
}}
v
-
btn
(
dark
color
=
'blue-grey darken-2'
...
...
@@ -111,16 +113,16 @@
depressed
)
v
-
icon
(
left
)
mdi
-
comment
span.text-none
Update Comment
.pt-5.text-center.body-2.blue-grey--text(v-else-if='permissions.write')
Be the first to comment.
.text-center.body-2.blue-grey--text(v-else)
No comments yet.
span
.
text
-
none
{{
$t
(
'common:comments.updateComment'
)
}}
.
pt
-
5
.
text
-
center
.
body
-
2
.
blue
-
grey
--
text
(
v
-
else
-
if
=
'permissions.write'
)
{{
$t
(
'common:comments.beFirst'
)
}}
.
text
-
center
.
body
-
2
.
blue
-
grey
--
text
(
v
-
else
)
{{
$t
(
'common:comments.none'
)
}}
v
-
dialog
(
v
-
model
=
'deleteCommentDialogShown'
,
max
-
width
=
'500'
)
v
-
card
.dialog-header.is-red
Confirm Delete
.
dialog
-
header
.
is
-
red
{{
$t
(
'common:comments.deleteConfirmTitle'
)
}}
v
-
card
-
text
.
pt
-
5
span
Are you sure you want to permanently delete this comment?
.caption: strong
This action cannot be undone!
span
{{
$t
(
'common:comments.deleteWarn'
)
}}
.
caption
:
strong
{{
$t
(
'common:comments.deletePermanentWarn'
)
}}
v
-
card
-
chin
v
-
spacer
v
-
btn
(
text
,
@
click
=
'deleteCommentDialogShown = false'
)
{{
$t
(
'common:actions.cancel'
)
}}
...
...
@@ -298,7 +300,7 @@ export default {
if
(
_
.
get
(
resp
,
'data.comments.create.responseResult.succeeded'
,
false
))
{
this
.
$store
.
commit
(
'showNotification'
,
{
style
:
'success'
,
message
:
'New comment posted successfully.'
,
message
:
this
.
$t
(
'common:comments.postSuccess'
)
,
icon
:
'check'
}
)
...
...
@@ -371,7 +373,7 @@ export default {
this
.
isBusy
=
true
try
{
if
(
this
.
commentEditContent
.
length
<
2
)
{
throw
new
Error
(
'Comment is empty or too short!'
)
throw
new
Error
(
this
.
$t
(
'common:comments.contentMissingError'
)
)
}
const
resp
=
await
this
.
$apollo
.
mutate
({
mutation
:
gql
`
...
...
@@ -404,7 +406,7 @@ export default {
if
(
_
.
get
(
resp
,
'data.comments.update.responseResult.succeeded'
,
false
))
{
this
.
$store
.
commit
(
'showNotification'
,
{
style
:
'success'
,
message
:
'Comment was updated successfully.'
,
message
:
this
.
$t
(
'common:comments.updateSuccess'
)
,
icon
:
'check'
}
)
...
...
@@ -470,7 +472,7 @@ export default {
if
(
_
.
get
(
resp
,
'data.comments.delete.responseResult.succeeded'
,
false
))
{
this
.
$store
.
commit
(
'showNotification'
,
{
style
:
'success'
,
message
:
'Comment was deleted successfully.'
,
message
:
this
.
$t
(
'common:comments.deleteSuccess'
)
,
icon
:
'check'
}
)
...
...
client/components/editor.vue
View file @
4855051d
...
...
@@ -115,6 +115,14 @@ export default {
type
:
String
,
default
:
''
},
publishStartDate
:
{
type
:
String
,
default
:
''
},
publishEndDate
:
{
type
:
String
,
default
:
''
},
scriptJs
:
{
type
:
String
,
default
:
''
...
...
@@ -196,6 +204,8 @@ export default {
this
.
$store
.
set
(
'page/id'
,
this
.
pageId
)
this
.
$store
.
set
(
'page/description'
,
this
.
description
)
this
.
$store
.
set
(
'page/isPublished'
,
this
.
isPublished
)
this
.
$store
.
set
(
'page/publishStartDate'
,
this
.
publishStartDate
)
this
.
$store
.
set
(
'page/publishEndDate'
,
this
.
publishEndDate
)
this
.
$store
.
set
(
'page/locale'
,
this
.
locale
)
this
.
$store
.
set
(
'page/path'
,
this
.
path
)
this
.
$store
.
set
(
'page/tags'
,
this
.
tags
)
...
...
client/components/editor/editor-modal-properties.vue
View file @
4855051d
...
...
@@ -136,12 +136,12 @@
)
v-spacer
v-btn(
flat=''
text
color='primary'
@click='isPublishStartShown = false'
)
{{
$t
(
'common:actions.cancel'
)
}}
v-btn(
flat=''
text
color='primary'
@click='$refs.menuPublishStart.save(publishStartDate)'
)
{{
$t
(
'common:actions.ok'
)
}}
...
...
@@ -177,12 +177,12 @@
)
v-spacer
v-btn(
flat=''
text
color='primary'
@click='isPublishEndShown = false'
)
{{
$t
(
'common:actions.cancel'
)
}}
v-btn(
flat=''
text
color='primary'
@click='$refs.menuPublishEnd.save(publishEndDate)'
)
{{
$t
(
'common:actions.ok'
)
}}
...
...
client/themes/default/components/page.vue
View file @
4855051d
...
...
@@ -260,12 +260,14 @@
v-icon(size='20') mdi-trash-can-outline
span
{{
$t
(
'common:header.delete'
)
}}
span
{{
$t
(
'common:page.editPage'
)
}}
v-alert.mb-5(v-if='!isPublished', color='red', outlined, icon='mdi-minus-circle', dense)
.caption
{{
$t
(
'common:page.unpublishedWarning'
)
}}
.contents(ref='container')
slot(name='contents')
.comments-container#discussion(v-if='commentsEnabled && commentsPerms.read')
.comments-header
v-icon.mr-2(dark) mdi-comment-text-outline
span
Comments
span
{{
$t
(
'common:comments.title'
)
}}
.comments-main
slot(name='comments')
nav-footer
...
...
@@ -503,7 +505,7 @@ export default {
this
.
$store
.
set
(
'page/title'
,
this
.
title
)
this
.
$store
.
set
(
'page/updatedAt'
,
this
.
updatedAt
)
if
(
this
.
effectivePermissions
)
{
this
.
$store
.
set
(
'page/effectivePermissions'
,
JSON
.
parse
(
Buffer
.
from
(
this
.
effectivePermissions
,
'base64'
).
toString
()))
this
.
$store
.
set
(
'page/effectivePermissions'
,
JSON
.
parse
(
Buffer
.
from
(
this
.
effectivePermissions
,
'base64'
).
toString
()))
}
this
.
$store
.
set
(
'page/mode'
,
'view'
)
...
...
server/controllers/common.js
View file @
4855051d
...
...
@@ -3,37 +3,12 @@ const router = express.Router()
const
pageHelper
=
require
(
'../helpers/page'
)
const
_
=
require
(
'lodash'
)
const
CleanCSS
=
require
(
'clean-css'
)
const
moment
=
require
(
'moment'
)
/* global WIKI */
const
tmplCreateRegex
=
/^
[
0-9
]
+
(
,
[
0-9
]
+
)?
$/
const
getPageEffectivePermissions
=
(
req
,
page
)
=>
{
return
{
comments
:
{
read
:
WIKI
.
config
.
features
.
featurePageComments
?
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:comments'
],
page
)
:
false
,
write
:
WIKI
.
config
.
features
.
featurePageComments
?
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:comments'
],
page
)
:
false
,
manage
:
WIKI
.
config
.
features
.
featurePageComments
?
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'manage:comments'
],
page
)
:
false
},
history
:
{
read
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:history'
],
page
)
},
source
:
{
read
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:source'
],
page
)
},
pages
:
{
write
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:pages'
],
page
),
manage
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'manage:pages'
],
page
),
delete
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'delete:pages'
],
page
),
script
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:scripts'
],
page
),
style
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:styles'
],
page
)
},
system
:
{
manage
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'manage:system'
],
page
)
}
}
}
/**
* Robots.txt
*/
...
...
@@ -137,6 +112,9 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
pageArgs
.
tags
=
_
.
get
(
page
,
'tags'
,
[])
// -> Effective Permissions
const
effectivePermissions
=
WIKI
.
auth
.
getEffectivePermissions
(
req
,
pageArgs
)
const
injectCode
=
{
css
:
WIKI
.
config
.
theming
.
injectCSS
,
head
:
WIKI
.
config
.
theming
.
injectHead
,
...
...
@@ -145,7 +123,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
if
(
page
)
{
// -> EDIT MODE
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:pages'
,
'manage:pages'
],
pageArgs
))
{
if
(
!
(
effectivePermissions
.
pages
.
write
||
effectivePermissions
.
pages
.
manage
))
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Unauthorized'
)
return
res
.
render
(
'unauthorized'
,
{
action
:
'edit'
})
}
...
...
@@ -166,7 +144,7 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
page
.
content
=
Buffer
.
from
(
page
.
content
).
toString
(
'base64'
)
}
else
{
// -> CREATE MODE
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:pages'
],
pageArgs
)
)
{
if
(
!
effectivePermissions
.
pages
.
write
)
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Unauthorized'
)
return
res
.
render
(
'unauthorized'
,
{
action
:
'create'
})
}
...
...
@@ -229,9 +207,6 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
}
}
// -> Effective Permissions
const
effectivePermissions
=
getPageEffectivePermissions
(
req
,
pageArgs
)
res
.
render
(
'editor'
,
{
page
,
injectCode
,
effectivePermissions
})
})
...
...
@@ -262,7 +237,9 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
pageArgs
.
tags
=
_
.
get
(
page
,
'tags'
,
[])
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:history'
],
pageArgs
))
{
const
effectivePermissions
=
WIKI
.
auth
.
getEffectivePermissions
(
req
,
pageArgs
)
if
(
!
effectivePermissions
.
history
.
read
)
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Unauthorized'
)
return
res
.
render
(
'unauthorized'
,
{
action
:
'history'
})
}
...
...
@@ -271,9 +248,6 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
page
.
title
)
_
.
set
(
res
.
locals
,
'pageMeta.description'
,
page
.
description
)
// -> Effective Permissions
const
effectivePermissions
=
getPageEffectivePermissions
(
req
,
pageArgs
)
res
.
render
(
'history'
,
{
page
,
effectivePermissions
})
}
else
{
res
.
redirect
(
`/
${
pageArgs
.
path
}
`
)
...
...
@@ -346,16 +320,19 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
return
res
.
redirect
(
`/s/
${
pageArgs
.
locale
}
/
${
pageArgs
.
path
}
`
)
}
// -> Effective Permissions
const
effectivePermissions
=
WIKI
.
auth
.
getEffectivePermissions
(
req
,
pageArgs
)
_
.
set
(
res
,
'locals.siteConfig.lang'
,
pageArgs
.
locale
)
_
.
set
(
res
,
'locals.siteConfig.rtl'
,
req
.
i18n
.
dir
()
===
'rtl'
)
if
(
versionId
>
0
)
{
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:history'
],
pageArgs
)
)
{
if
(
!
effectivePermissions
.
history
.
read
)
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Unauthorized'
)
return
res
.
render
(
'unauthorized'
,
{
action
:
'sourceVersion'
})
}
}
else
{
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:source'
],
pageArgs
)
)
{
if
(
!
effectivePermissions
.
source
.
read
)
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Unauthorized'
)
return
res
.
render
(
'unauthorized'
,
{
action
:
'source'
})
}
...
...
@@ -376,9 +353,6 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
page
.
title
)
_
.
set
(
res
.
locals
,
'pageMeta.description'
,
page
.
description
)
// -> Effective Permissions
const
effectivePermissions
=
getPageEffectivePermissions
(
req
,
pageArgs
)
res
.
render
(
'source'
,
{
page
,
effectivePermissions
})
}
}
else
{
...
...
@@ -419,8 +393,11 @@ router.get('/*', async (req, res, next) => {
})
pageArgs
.
tags
=
_
.
get
(
page
,
'tags'
,
[])
// -> Effective Permissions
const
effectivePermissions
=
WIKI
.
auth
.
getEffectivePermissions
(
req
,
pageArgs
)
// -> Check User Access
if
(
!
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:pages'
],
pageArgs
)
)
{
if
(
!
effectivePermissions
.
pages
.
read
)
{
if
(
req
.
user
.
id
===
2
)
{
res
.
cookie
(
'loginRedirect'
,
req
.
path
,
{
maxAge
:
15
*
60
*
1000
...
...
@@ -442,6 +419,21 @@ router.get('/*', async (req, res, next) => {
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
page
.
title
)
_
.
set
(
res
.
locals
,
'pageMeta.description'
,
page
.
description
)
// -> Check Publishing State
let
pageIsPublished
=
page
.
isPublished
if
(
pageIsPublished
&&
!
_
.
isEmpty
(
page
.
publishStartDate
))
{
pageIsPublished
=
moment
(
page
.
publishStartDate
).
isSameOrBefore
()
}
if
(
pageIsPublished
&&
!
_
.
isEmpty
(
page
.
publishEndDate
))
{
pageIsPublished
=
moment
(
page
.
publishEndDate
).
isSameOrAfter
()
}
if
(
!
pageIsPublished
&&
!
effectivePermissions
.
pages
.
write
)
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Unauthorized'
)
return
res
.
status
(
403
).
render
(
'unauthorized'
,
{
action
:
'view'
})
}
// -> Build sidebar navigation
let
sdi
=
1
const
sidebar
=
(
await
WIKI
.
models
.
navigation
.
getTree
({
cache
:
true
,
locale
:
pageArgs
.
locale
,
groups
:
req
.
user
.
groups
})).
map
(
n
=>
({
...
...
@@ -499,9 +491,6 @@ router.get('/*', async (req, res, next) => {
})
}
// -> Effective Permissions
const
effectivePermissions
=
getPageEffectivePermissions
(
req
,
pageArgs
)
// -> Render view
res
.
render
(
'page'
,
{
page
,
...
...
@@ -516,7 +505,7 @@ router.get('/*', async (req, res, next) => {
res
.
render
(
'welcome'
,
{
locale
:
pageArgs
.
locale
})
}
else
{
_
.
set
(
res
.
locals
,
'pageMeta.title'
,
'Page Not Found'
)
if
(
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:pages'
],
pageArgs
)
)
{
if
(
effectivePermissions
.
pages
.
write
)
{
res
.
status
(
404
).
render
(
'new'
,
{
path
:
pageArgs
.
path
,
locale
:
pageArgs
.
locale
})
}
else
{
res
.
status
(
404
).
render
(
'notfound'
,
{
action
:
'view'
})
...
...
server/core/auth.js
View file @
4855051d
...
...
@@ -380,5 +380,35 @@ module.exports = {
WIKI
.
events
.
inbound
.
on
(
'reloadAuthStrategies'
,
()
=>
{
WIKI
.
auth
.
activateStrategies
()
})
},
/**
* Get all user permissions for a specific page
*/
getEffectivePermissions
(
req
,
page
)
{
return
{
comments
:
{
read
:
WIKI
.
config
.
features
.
featurePageComments
?
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:comments'
],
page
)
:
false
,
write
:
WIKI
.
config
.
features
.
featurePageComments
?
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:comments'
],
page
)
:
false
,
manage
:
WIKI
.
config
.
features
.
featurePageComments
?
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'manage:comments'
],
page
)
:
false
},
history
:
{
read
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:history'
],
page
)
},
source
:
{
read
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:source'
],
page
)
},
pages
:
{
read
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'read:pages'
],
page
),
write
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:pages'
],
page
),
manage
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'manage:pages'
],
page
),
delete
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'delete:pages'
],
page
),
script
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:scripts'
],
page
),
style
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'write:styles'
],
page
)
},
system
:
{
manage
:
WIKI
.
auth
.
checkAccess
(
req
.
user
,
[
'manage:system'
],
page
)
}
}
}
}
server/views/editor.pug
View file @
4855051d
...
...
@@ -14,6 +14,8 @@ block body
description=page.description
:tags=page.tags
:is-published=page.isPublished
publish-start-date=page.publishStartDate
publish-end-date=page.publishEndDate
script-css=page.extra.css
script-js=page.extra.js
init-mode=page.mode
...
...
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