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
0b935446
You need to sign in or sign up before continuing.
Commit
0b935446
authored
Sep 08, 2018
by
Nicolas Giard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: admin rendering UI + module configuration UI + UI fixes
parent
92d0925d
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
202 additions
and
52 deletions
+202
-52
admin.vue
client/components/admin.vue
+11
-0
admin-api.vue
client/components/admin/admin-api.vue
+1
-0
admin-auth.vue
client/components/admin/admin-auth.vue
+1
-0
admin-contribute.vue
client/components/admin/admin-contribute.vue
+1
-0
admin-dev.vue
client/components/admin/admin-dev.vue
+1
-0
admin-editor.vue
client/components/admin/admin-editor.vue
+1
-0
admin-general.vue
client/components/admin/admin-general.vue
+1
-0
admin-groups-edit.vue
client/components/admin/admin-groups-edit.vue
+1
-0
admin-groups.vue
client/components/admin/admin-groups.vue
+1
-0
admin-locale.vue
client/components/admin/admin-locale.vue
+1
-0
admin-logging.vue
client/components/admin/admin-logging.vue
+1
-0
admin-pages.vue
client/components/admin/admin-pages.vue
+79
-0
admin-rendering.vue
client/components/admin/admin-rendering.vue
+93
-50
admin-search.vue
client/components/admin/admin-search.vue
+1
-0
admin-stats.vue
client/components/admin/admin-stats.vue
+1
-0
admin-storage.vue
client/components/admin/admin-storage.vue
+1
-0
admin-system.vue
client/components/admin/admin-system.vue
+1
-0
admin-theme.vue
client/components/admin/admin-theme.vue
+1
-0
admin-users.vue
client/components/admin/admin-users.vue
+1
-0
admin-utilities.vue
client/components/admin/admin-utilities.vue
+1
-0
nav-header.vue
client/components/common/nav-header.vue
+1
-1
definition.yml
server/modules/renderer/html-blockquotes/definition.yml
+1
-1
No files found.
client/components/admin.vue
View file @
0b935446
...
...
@@ -14,6 +14,9 @@
v-list-tile(to='/locale')
v-list-tile-avatar: v-icon language
v-list-tile-title
{{
$t
(
'admin:locale.title'
)
}}
v-list-tile(to='/pages')
v-list-tile-avatar: v-icon insert_drive_file
v-list-tile-title
{{
$t
(
'admin:pages.title'
)
}}
v-list-tile(to='/stats')
v-list-tile-avatar: v-icon show_chart
v-list-tile-title
{{
$t
(
'admin:stats.title'
)
}}
...
...
@@ -90,6 +93,7 @@ const router = new VueRouter({
{
path
:
'/dashboard'
,
component
:
()
=>
import
(
/* webpackChunkName: "admin" */
'./admin/admin-dashboard.vue'
)
},
{
path
:
'/general'
,
component
:
()
=>
import
(
/* webpackChunkName: "admin" */
'./admin/admin-general.vue'
)
},
{
path
:
'/locale'
,
component
:
()
=>
import
(
/* webpackChunkName: "admin" */
'./admin/admin-locale.vue'
)
},
{
path
:
'/pages'
,
component
:
()
=>
import
(
/* webpackChunkName: "admin" */
'./admin/admin-pages.vue'
)
},
{
path
:
'/stats'
,
component
:
()
=>
import
(
/* webpackChunkName: "admin" */
'./admin/admin-stats.vue'
)
},
{
path
:
'/theme'
,
component
:
()
=>
import
(
/* webpackChunkName: "admin" */
'./admin/admin-theme.vue'
)
},
{
path
:
'/groups'
,
component
:
()
=>
import
(
/* webpackChunkName: "admin" */
'./admin/admin-groups.vue'
)
},
...
...
@@ -154,4 +158,11 @@ export default {
}
}
.admin-header-icon
{
position
:
absolute
;
top
:
1rem
;
right
:
1rem
;
}
</
style
>
client/components/admin/admin-api.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(flat)
v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') call_split
.headline.blue--text.text--darken-2 API
.subheading.grey--text Manage keys to access the API
v-card
...
...
client/components/admin/admin-auth.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
.pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') lock_outline
.headline.primary--text Authentication
.subheading.grey--text Configure the authentication settings of your wiki
v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)
...
...
client/components/admin/admin-contribute.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(flat)
v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') favorite
.headline.primary--text
{{
$t
(
'admin:contribute.title'
)
}}
.subheading.grey--text
{{
$t
(
'admin:contribute.subtitle'
)
}}
v-card.pa-3
...
...
client/components/admin/admin-dev.vue
View file @
0b935446
<
template
lang=
'pug'
>
div
v-card(flat, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') weekend
.headline.primary--text Developer Tools
.subheading.grey--text ¯\_(ツ)_/¯
v-tabs(
...
...
client/components/admin/admin-editor.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
.pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') transform
.headline.primary--text Editor
.subheading.grey--text Configure the content editor
v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)
...
...
client/components/admin/admin-general.vue
View file @
0b935446
...
...
@@ -2,6 +2,7 @@
v-container(fluid, fill-height, grid-list-lg)
v-layout(row wrap)
v-flex(xs12)
.admin-header-icon: v-icon(size='80', color='grey lighten-2') widgets
.headline.primary--text
{{
$t
(
'admin:general.title'
)
}}
.subheading.grey--text
{{
$t
(
'admin:general.subtitle'
)
}}
v-form.pt-3
...
...
client/components/admin/admin-groups-edit.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card
v-card(flat, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') people
.headline.blue--text.text--darken-2 Edit Group
.subheading.grey--text
{{
name
}}
v-btn(color='primary', fab, absolute, bottom, right, small, to='/groups'): v-icon arrow_upward
...
...
client/components/admin/admin-groups.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(flat)
v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') people
.headline.blue--text.text--darken-2 Groups
.subheading.grey--text Manage groups and their permissions
v-card
...
...
client/components/admin/admin-locale.vue
View file @
0b935446
...
...
@@ -2,6 +2,7 @@
v-container(fluid, fill-height, grid-list-lg)
v-layout(row wrap)
v-flex(xs12)
.admin-header-icon: v-icon(size='80', color='grey lighten-2') language
.headline.primary--text
{{
$t
(
'admin:locale.title'
)
}}
.subheading.grey--text
{{
$t
(
'admin:locale.subtitle'
)
}}
v-form.pt-3
...
...
client/components/admin/admin-logging.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
.pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') graphic_eq
.headline.primary--text Logging
.subheading.grey--text Configure the system logger(s)
v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)
...
...
client/components/admin/admin-pages.vue
0 → 100644
View file @
0b935446
<
template
lang=
'pug'
>
v-card(flat)
v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') insert_drive_file
.headline.blue--text.text--darken-2 Pages
.subheading.grey--text Manage pages
v-card
v-card-title
v-btn(color='primary', dark, slot='activator')
v-icon(left) add
| New Page
v-btn(icon, @click='refresh')
v-icon.grey--text refresh
v-spacer
v-text-field(solo, append-icon='search', label='Search', single-line, hide-details, v-model='search')
v-data-table(
:items='groups'
:headers='headers'
:search='search'
:pagination.sync='pagination'
:rows-per-page-items='[15]'
hide-actions
)
template(slot='items', slot-scope='props')
tr.is-clickable(:active='props.selected', @click='$router.push("/e/" + props.item.id)')
td.text-xs-right
{{
props
.
item
.
id
}}
td
{{
props
.
item
.
name
}}
td
{{
props
.
item
.
userCount
}}
td
{{
props
.
item
.
createdAt
|
moment
(
'calendar'
)
}}
td
{{
props
.
item
.
updatedAt
|
moment
(
'calendar'
)
}}
template(slot='no-data')
v-alert.ma-3(icon='warning', :value='true', outline) No pages to display.
.text-xs-center.py-2(v-if='groups.length > 15')
v-pagination(v-model='pagination.page', :length='pages')
</
template
>
<
script
>
export
default
{
data
()
{
return
{
selectedGroup
:
{},
pagination
:
{},
groups
:
[],
headers
:
[
{
text
:
'ID'
,
value
:
'id'
,
width
:
50
,
align
:
'right'
},
{
text
:
'Title'
,
value
:
'title'
},
{
text
:
'Path'
,
value
:
'path'
},
{
text
:
'Created'
,
value
:
'createdAt'
,
width
:
250
},
{
text
:
'Last Updated'
,
value
:
'updatedAt'
,
width
:
250
}
],
search
:
''
}
},
computed
:
{
pages
()
{
if
(
this
.
pagination
.
rowsPerPage
==
null
||
this
.
pagination
.
totalItems
==
null
)
{
return
0
}
return
Math
.
ceil
(
this
.
pagination
.
totalItems
/
this
.
pagination
.
rowsPerPage
)
}
},
methods
:
{
async
refresh
()
{
// await this.$apollo.queries.groups.refetch()
this
.
$store
.
commit
(
'showNotification'
,
{
message
:
'Pages have been refreshed.'
,
style
:
'success'
,
icon
:
'cached'
})
}
}
}
</
script
>
<
style
lang=
'scss'
>
</
style
>
client/components/admin/admin-rendering.vue
View file @
0b935446
...
...
@@ -2,6 +2,7 @@
v-container(fluid, fill-height, grid-list-lg)
v-layout(row wrap)
v-flex(xs12)
.admin-header-icon: v-icon(size='80', color='grey lighten-2') system_update_alt
.headline.primary--text Rendering
.subheading.grey--text Configure how content is rendered
v-layout.mt-3(row wrap)
...
...
@@ -17,7 +18,7 @@
v-expansion-panel.adm-rendering-pipeline(v-model='selectedCore')
v-expansion-panel-content(
hide-actions
v-for='core in
core
s'
v-for='core in
renderer
s'
:key='core.key'
)
v-toolbar(
...
...
@@ -27,24 +28,27 @@
dark
flat
)
v-spacer
.body-2
{{
core
.
input
}}
v-icon.mx-2 arrow_forward
.caption
{{
core
.
output
}}
v-list(two-line, dense)
v-spacer
v-list.py-0(two-line, dense)
template(v-for='(rdr, n) in core.children')
v-list-tile(
avatar
:key='rdr.key'
@click=''
@click='selectRenderer(rdr.key)'
:class='currentRenderer.key === rdr.key ? "blue lighten-5" : ""'
)
v-list-tile-avatar
v-icon(
color='grey
')
{{
rdr
.
icon
}}
v-icon(
:color='currentRenderer.key === rdr.key ? "primary" : "grey"
')
{{
rdr
.
icon
}}
v-list-tile-content
v-list-tile-title
{{
rdr
.
title
}}
v-list-tile-sub-title
{{
rdr
.
description
}}
v-list-tile-avatar
v-icon(color='green', small, v-if='rdr.isEnabled') lens
v-icon(color='red', small, v-else) trip_origin
status-indicator(v-if='rdr.isEnabled', positive, pulse)
status-indicator(v-else, negative, pulse)
v-divider.my-0(v-if='n < core.children.length - 1')
v-flex(lg9 xs12)
...
...
@@ -55,90 +59,129 @@
flat
dense
)
v-icon.mr-2 settings_applications
.subheading Markdown
v-icon chevron_right
.subheading Core
v-icon.mr-2
{{
currentRenderer
.
icon
}}
.subheading
{{
currentRenderer
.
title
}}
v-spacer
v-btn(flat, disabled)
v-icon(left) wrap_text
span Bypass
v-btn(flat, disabled)
v-icon(left) clear
span Remove
v-card-text
.pt-3.mt-1
v-switch(
v-model='linkify'
label='Automatically convert links'
color='primary'
persistent-hint
hint='Links will automatically be converted to clickable links.'
dark
color='white'
label='Enabled'
v-model='currentRenderer.isEnabled'
)
v-divider.mt-3
v-switch(
v-model='linkify'
label='Automatically convert line breaks'
color='primary'
v-card-text.pb-4.pt-2.pl-4
v-subheader.pl-0 Rendering Module Configuration
.body-1.ml-3(v-if='!currentRenderer.config || currentRenderer.config.length < 1') This rendering module has no configuration options you can modify.
template(v-else, v-for='(cfg, idx) in currentRenderer.config')
v-select(
v-if='cfg.value.type === "string" && cfg.value.enum'
outline
background-color='grey lighten-2'
:items='cfg.value.enum'
:key='cfg.key'
:label='cfg.value.title'
v-model='cfg.value.value'
:hint='cfg.value.hint ? cfg.value.hint : ""'
persistent-hint
hint='Add linebreaks within paragraphs.
'
:class='cfg.value.hint ? "mb-2" : ""
'
)
v-divider.mt-3
v-switch(
v-model='linkify'
label='Highlight code blocks'
v-else-if='cfg.value.type === "boolean"'
:key='cfg.key'
:label='cfg.value.title'
v-model='cfg.value.value'
color='primary'
:hint='cfg.value.hint ? cfg.value.hint : ""'
persistent-hint
hint='Add syntax coloring to code blocks.'
)
v-select.mt-3(
:items='["Light", "Dark"]'
v-model='codeTheme'
label='Code Color Theme'
v-text-field(
v-else
outline
background-color='grey lighten-2'
:key='cfg.key'
:label='cfg.value.title'
v-model='cfg.value.value'
:hint='cfg.value.hint ? cfg.value.hint : ""'
persistent-hint
:class='cfg.value.hint ? "mb-2" : ""'
)
v-divider.my-3(v-if='idx < currentRenderer.config.length - 1')
v-card-chin
v-btn(
color='primary'
)
v-icon(left) check
span Apply Configuration
v-spacer
.caption.pr-3.grey--text Module:
{{
currentRenderer
.
key
}}
</
template
>
<
script
>
import
_
from
'lodash'
import
{
DepGraph
}
from
'dependency-graph'
import
{
StatusIndicator
}
from
'vue-status-indicator'
import
renderersQuery
from
'gql/admin/rendering/rendering-query-renderers.gql'
export
default
{
components
:
{
StatusIndicator
},
data
()
{
return
{
selectedCore
:
0
,
linkify
:
true
,
codeTheme
:
'Light'
,
renderers
:
[]
}
},
computed
:
{
cores
()
{
return
_
.
filter
(
this
.
renderers
,
[
'dependsOn'
,
null
]).
map
(
core
=>
{
core
.
children
=
_
.
concat
([
_
.
cloneDeep
(
core
)],
_
.
filter
(
this
.
renderers
,
[
'dependsOn'
,
core
.
key
]))
return
core
})
selectedCore
:
-
1
,
renderers
:
[],
currentRenderer
:
{}
}
},
watch
:
{
renderers
(
newValue
,
oldValue
)
{
_
.
delay
(()
=>
{
this
.
selectedCore
=
_
.
findIndex
(
this
.
cores
,
[
'key'
,
'markdownCore'
])
this
.
selectedCore
=
_
.
findIndex
(
newValue
,
[
'key'
,
'markdownCore'
])
this
.
selectRenderer
(
'markdownCore'
)
},
500
)
}
},
methods
:
{
selectRenderer
(
key
)
{
this
.
renderers
.
map
(
rdr
=>
{
if
(
_
.
some
(
rdr
.
children
,
[
'key'
,
key
]))
{
this
.
currentRenderer
=
_
.
find
(
rdr
.
children
,
[
'key'
,
key
])
}
})
}
},
apollo
:
{
renderers
:
{
query
:
renderersQuery
,
fetchPolicy
:
'network-only'
,
update
:
(
data
)
=>
_
.
cloneDeep
(
data
.
rendering
.
renderers
).
map
(
str
=>
({...
str
,
config
:
str
.
config
.
map
(
cfg
=>
({...
cfg
,
value
:
JSON
.
parse
(
cfg
.
value
)}))})),
update
:
(
data
)
=>
{
let
renderers
=
_
.
cloneDeep
(
data
.
rendering
.
renderers
).
map
(
str
=>
({...
str
,
config
:
str
.
config
.
map
(
cfg
=>
({...
cfg
,
value
:
JSON
.
parse
(
cfg
.
value
)}))}))
// Build tree
const
graph
=
new
DepGraph
({
circular
:
true
})
const
rawCores
=
_
.
filter
(
renderers
,
[
'dependsOn'
,
null
]).
map
(
core
=>
{
core
.
children
=
_
.
concat
([
_
.
cloneDeep
(
core
)],
_
.
filter
(
renderers
,
[
'dependsOn'
,
core
.
key
]))
return
core
})
// Build dependency graph
rawCores
.
map
(
core
=>
{
graph
.
addNode
(
core
.
key
)
})
rawCores
.
map
(
core
=>
{
rawCores
.
map
(
coreTarget
=>
{
if
(
core
.
key
!==
coreTarget
.
key
)
{
if
(
core
.
output
===
coreTarget
.
input
)
{
graph
.
addDependency
(
core
.
key
,
coreTarget
.
key
)
}
}
})
})
// Reorder cores in reverse dependency order
let
orderedCores
=
[]
_
.
reverse
(
graph
.
overallOrder
()).
map
(
coreKey
=>
{
orderedCores
.
push
(
_
.
find
(
rawCores
,
[
'key'
,
coreKey
]))
})
return
orderedCores
},
watchLoading
(
isLoading
)
{
this
.
$store
.
commit
(
`loading
${
isLoading
?
'Start'
:
'Stop'
}
`
,
'admin-rendering-refresh'
)
}
...
...
client/components/admin/admin-search.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
.pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') search
.headline.primary--text Search Engine
.subheading.grey--text Configure the search capabilities of your wiki
v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)
...
...
client/components/admin/admin-stats.vue
View file @
0b935446
...
...
@@ -2,6 +2,7 @@
v-container(fluid, fill-height)
v-layout(row wrap)
v-flex(xs12)
.admin-header-icon: v-icon(size='80', color='grey lighten-2') show_chart
.headline.primary--text Statistics
.subheading.grey--text Useful information about your wiki
.pa-3
...
...
client/components/admin/admin-storage.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"')
.pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') storage
.headline.primary--text Storage
.subheading.grey--text Set backup and sync targets for your content
v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)
...
...
client/components/admin/admin-system.vue
View file @
0b935446
...
...
@@ -2,6 +2,7 @@
v-container(fluid, fill-height, grid-list-lg)
v-layout(row, wrap)
v-flex(xs12)
.admin-header-icon: v-icon(size='80', color='grey lighten-2') tune
.headline.primary--text
{{
$t
(
'admin:system.title'
)
}}
.subheading.grey--text
{{
$t
(
'admin:system.subtitle'
)
}}
v-layout.mt-3(row wrap)
...
...
client/components/admin/admin-theme.vue
View file @
0b935446
...
...
@@ -2,6 +2,7 @@
v-container(fluid, fill-height, grid-list-lg)
v-layout(row wrap)
v-flex(xs12)
.admin-header-icon: v-icon(size='80', color='grey lighten-2') palette
.headline.primary--text Theme
.subheading.grey--text Modify the look & feel of your wiki
v-form.pt-3
...
...
client/components/admin/admin-users.vue
View file @
0b935446
<
template
lang=
'pug'
>
v-card(flat)
v-card(flat, tile, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') perm_identity
.headline.blue--text.text--darken-2 Users
.subheading.grey--text Manage users
v-card
...
...
client/components/admin/admin-utilities.vue
View file @
0b935446
<
template
lang=
'pug'
>
div
v-card(flat, :color='$vuetify.dark ? "grey darken-4" : "grey lighten-5"').pa-3.pt-4
.admin-header-icon: v-icon(size='80', color='grey lighten-2') build
.headline.primary--text Utilities
.subheading.grey--text Maintenance and troubleshooting tools
v-tabs(:color='$vuetify.dark ? "primary" : "grey lighten-4"', fixed-tabs, :slider-color='$vuetify.dark ? "white" : "primary"', show-arrows)
...
...
client/components/common/nav-header.vue
View file @
0b935446
...
...
@@ -19,7 +19,7 @@
v-menu(open-on-hover, offset-y, bottom, left, min-width='250')
v-toolbar-side-icon(slot='activator')
v-icon view_module
v-list(dense).py-0
v-list(dense
, :light='!$vuetify.dark'
).py-0
v-list-tile(avatar, href='/')
v-list-tile-avatar: v-icon(color='blue') home
v-list-tile-content Home
...
...
server/modules/renderer/html-blockquotes/definition.yml
View file @
0b935446
key
:
htmlBlockquotes
title
:
Blockquotes
description
:
Embed audio players for audio content
description
:
Parse blockquotes box styling
author
:
requarks.io
icon
:
insert_comment
enabledDefault
:
true
...
...
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