Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
a9deabea
Commit
a9deabea
authored
Mar 10, 2017
by
Phil Hughes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added filter bar into add issues modal
[ci skip]
parent
68e64a5b
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
60 additions
and
309 deletions
+60
-309
app/assets/javascripts/boards/components/modal/filters.js
app/assets/javascripts/boards/components/modal/filters.js
+6
-42
app/assets/javascripts/boards/components/modal/filters/label.js
...sets/javascripts/boards/components/modal/filters/label.js
+0
-54
app/assets/javascripts/boards/components/modal/filters/milestone.js
.../javascripts/boards/components/modal/filters/milestone.js
+0
-56
app/assets/javascripts/boards/components/modal/filters/user.js
...ssets/javascripts/boards/components/modal/filters/user.js
+0
-96
app/assets/javascripts/boards/components/modal/header.js
app/assets/javascripts/boards/components/modal/header.js
+2
-11
app/assets/javascripts/boards/filtered_search_boards.js
app/assets/javascripts/boards/filtered_search_boards.js
+2
-2
app/assets/javascripts/filtered_search/dropdown_hint.js
app/assets/javascripts/filtered_search/dropdown_hint.js
+1
-1
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
...ripts/filtered_search/filtered_search_dropdown_manager.js
+14
-12
app/assets/javascripts/filtered_search/filtered_search_manager.js
...ts/javascripts/filtered_search/filtered_search_manager.js
+7
-6
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
...ascripts/filtered_search/filtered_search_visual_tokens.js
+7
-7
app/assets/stylesheets/pages/boards.scss
app/assets/stylesheets/pages/boards.scss
+2
-6
app/views/projects/boards/_show.html.haml
app/views/projects/boards/_show.html.haml
+1
-0
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+18
-16
No files found.
app/assets/javascripts/boards/components/modal/filters.js
View file @
a9deabea
/* global Vue */
const
userFilter
=
require
(
'
./filters/user
'
);
const
milestoneFilter
=
require
(
'
./filters/milestone
'
);
const
labelFilter
=
require
(
'
./filters/label
'
);
import
FilteredSearchBoards
from
'
../../filtered_search_boards
'
;
module
.
exports
=
Vue
.
extend
(
{
export
default
{
name
:
'
modal-filters
'
,
props
:
{
projectId
:
{
type
:
Number
,
required
:
true
,
},
milestonePath
:
{
type
:
String
,
required
:
true
,
},
labelPath
:
{
type
:
String
,
required
:
true
,
},
mounted
()
{
this
.
filteredSearch
=
new
FilteredSearchBoards
({
path
:
''
},
false
,
this
.
$el
);
},
destroyed
()
{
gl
.
issueBoards
.
ModalStore
.
setDefaultFilter
();
},
components
:
{
userFilter
,
milestoneFilter
,
labelFilter
,
},
template
:
`
<div class="modal-filters">
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-user-search js-author-search"
toggle-label="Author"
field-name="author_id"
:project-id="projectId"></user-filter>
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-assignee-search"
toggle-label="Assignee"
field-name="assignee_id"
:null-user="true"
:project-id="projectId"></user-filter>
<milestone-filter :milestone-path="milestonePath"></milestone-filter>
<label-filter :label-path="labelPath"></label-filter>
</div>
`
,
});
template
:
'
#js-board-modal-filter
'
,
};
app/assets/javascripts/boards/components/modal/filters/label.js
deleted
100644 → 0
View file @
68e64a5b
/* eslint-disable no-new */
/* global Vue */
/* global LabelsSelect */
module
.
exports
=
Vue
.
extend
({
name
:
'
filter-label
'
,
props
:
{
labelPath
:
{
type
:
String
,
required
:
true
,
},
},
mounted
()
{
new
LabelsSelect
(
this
.
$refs
.
dropdown
);
},
template
:
`
<div class="dropdown">
<button
class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-no="true"
:data-labels="labelPath"
ref="dropdown">
<span class="dropdown-toggle-text">
Label
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
<div class="dropdown-title">
Filter by label
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`
,
});
app/assets/javascripts/boards/components/modal/filters/milestone.js
deleted
100644 → 0
View file @
68e64a5b
/* eslint-disable no-new */
/* global Vue */
/* global MilestoneSelect */
module
.
exports
=
Vue
.
extend
({
name
:
'
filter-milestone
'
,
props
:
{
milestonePath
:
{
type
:
String
,
required
:
true
,
},
},
mounted
()
{
new
MilestoneSelect
(
null
,
this
.
$refs
.
dropdown
);
},
template
:
`
<div class="dropdown">
<button
class="dropdown-menu-toggle js-milestone-select"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-upcoming="true"
data-show-started="true"
data-field-name="milestone_title"
:data-milestones="milestonePath"
ref="dropdown">
<span class="dropdown-toggle-text">
Milestone
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
<div class="dropdown-title">
<span>Filter by milestone</span>
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search milestones"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`
,
});
app/assets/javascripts/boards/components/modal/filters/user.js
deleted
100644 → 0
View file @
68e64a5b
/* eslint-disable no-new */
/* global Vue */
/* global UsersSelect */
module
.
exports
=
Vue
.
extend
({
name
:
'
filter-user
'
,
props
:
{
toggleClassName
:
{
type
:
String
,
required
:
true
,
},
dropdownClassName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
toggleLabel
:
{
type
:
String
,
required
:
true
,
},
fieldName
:
{
type
:
String
,
required
:
true
,
},
nullUser
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
projectId
:
{
type
:
Number
,
required
:
true
,
},
},
mounted
()
{
new
UsersSelect
(
null
,
this
.
$refs
.
dropdown
);
},
computed
:
{
currentUsername
()
{
return
gon
.
current_username
;
},
dropdownTitle
()
{
return
`Filter by
${
this
.
toggleLabel
.
toLowerCase
()}
`
;
},
inputPlaceholder
()
{
return
`Search
${
this
.
toggleLabel
.
toLowerCase
()}
`
;
},
},
template
:
`
<div class="dropdown">
<button
class="dropdown-menu-toggle js-user-search"
:class="toggleClassName"
type="button"
data-toggle="dropdown"
data-current-user="true"
:data-any-user="'Any ' + toggleLabel"
:data-null-user="nullUser"
:data-field-name="fieldName"
:data-project-id="projectId"
:data-first-user="currentUsername"
ref="dropdown">
<span class="dropdown-toggle-text">
{{ toggleLabel }}
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div
class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
:class="dropdownClassName">
<div class="dropdown-title">
{{ dropdownTitle }}
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
autocomplete="off"
:placeholder="inputPlaceholder" />
<i class="fa fa-search dropdown-input-search"></i>
<i
role="button"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`
,
});
app/assets/javascripts/boards/components/modal/header.js
View file @
a9deabea
/* global Vue */
require
(
'
./tabs
'
);
const
modalFilters
=
require
(
'
./filters
'
)
;
import
modalFilters
from
'
./filters
'
;
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
...
...
@@ -66,16 +66,7 @@ const modalFilters = require('./filters');
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
<modal-filters
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input
placeholder="Search issues..."
class="form-control"
type="search"
v-model="searchTerm" />
<modal-filters />
<button
type="button"
class="btn btn-success btn-inverted prepend-left-10"
...
...
app/assets/javascripts/boards/filtered_search_boards.js
View file @
a9deabea
export
default
class
FilteredSearchBoards
extends
gl
.
FilteredSearchManager
{
constructor
(
store
,
updateUrl
=
false
)
{
super
(
'
boards
'
);
constructor
(
store
,
updateUrl
=
false
,
container
=
document
)
{
super
(
'
boards
'
,
container
);
this
.
store
=
store
;
this
.
updateUrl
=
updateUrl
;
...
...
app/assets/javascripts/filtered_search/dropdown_hint.js
View file @
a9deabea
...
...
@@ -45,7 +45,7 @@ require('./filtered_search_dropdown');
gl
.
FilteredSearchVisualTokens
.
addSearchVisualToken
(
searchTerms
.
join
(
'
'
));
}
gl
.
FilteredSearchDropdownManager
.
addWordToInput
(
token
.
replace
(
'
:
'
,
''
));
gl
.
FilteredSearchDropdownManager
.
addWordToInput
(
token
.
replace
(
'
:
'
,
''
)
,
''
,
false
,
this
.
container
);
}
this
.
dismissDropdown
();
this
.
dispatchInputEvent
();
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
View file @
a9deabea
...
...
@@ -2,11 +2,12 @@
(()
=>
{
class
FilteredSearchDropdownManager
{
constructor
(
baseEndpoint
=
''
,
page
)
{
constructor
(
baseEndpoint
=
''
,
page
,
container
)
{
this
.
container
=
container
;
this
.
baseEndpoint
=
baseEndpoint
.
replace
(
/
\/
$/
,
''
);
this
.
tokenizer
=
gl
.
FilteredSearchTokenizer
;
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeys
;
this
.
filteredSearchInput
=
document
.
querySelector
(
'
.filtered-search
'
);
this
.
filteredSearchInput
=
this
.
container
.
querySelector
(
'
.filtered-search
'
);
this
.
page
=
page
;
this
.
setupMapping
();
...
...
@@ -31,37 +32,37 @@
author
:
{
reference
:
null
,
gl
:
'
DropdownUser
'
,
element
:
document
.
querySelector
(
'
#js-dropdown-author
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-author
'
),
},
assignee
:
{
reference
:
null
,
gl
:
'
DropdownUser
'
,
element
:
document
.
querySelector
(
'
#js-dropdown-assignee
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-assignee
'
),
},
milestone
:
{
reference
:
null
,
gl
:
'
DropdownNonUser
'
,
extraArguments
:
[
`
${
this
.
baseEndpoint
}
/milestones.json`
,
'
%
'
],
element
:
document
.
querySelector
(
'
#js-dropdown-milestone
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-milestone
'
),
},
label
:
{
reference
:
null
,
gl
:
'
DropdownNonUser
'
,
extraArguments
:
[
`
${
this
.
baseEndpoint
}
/labels.json`
,
'
~
'
],
element
:
document
.
querySelector
(
'
#js-dropdown-label
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-label
'
),
},
hint
:
{
reference
:
null
,
gl
:
'
DropdownHint
'
,
element
:
document
.
querySelector
(
'
#js-dropdown-hint
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-hint
'
),
},
};
}
static
addWordToInput
(
tokenName
,
tokenValue
=
''
,
clicked
=
false
)
{
const
input
=
document
.
querySelector
(
'
.filtered-search
'
);
static
addWordToInput
(
tokenName
,
tokenValue
=
''
,
clicked
=
false
,
container
=
document
)
{
const
input
=
container
.
querySelector
(
'
.filtered-search
'
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
tokenName
,
tokenValue
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
tokenName
,
tokenValue
,
container
);
input
.
value
=
''
;
if
(
clicked
)
{
...
...
@@ -75,13 +76,13 @@
updateDropdownOffset
(
key
)
{
// Always align dropdown with the input field
let
offset
=
this
.
filteredSearchInput
.
getBoundingClientRect
().
left
-
document
.
querySelector
(
'
.scroll-container
'
).
getBoundingClientRect
().
left
;
let
offset
=
this
.
filteredSearchInput
.
getBoundingClientRect
().
left
-
this
.
container
.
querySelector
(
'
.scroll-container
'
).
getBoundingClientRect
().
left
;
const
maxInputWidth
=
240
;
const
currentDropdownWidth
=
this
.
mapping
[
key
].
element
.
clientWidth
||
maxInputWidth
;
// Make sure offset never exceeds the input container
const
offsetMaxWidth
=
document
.
querySelector
(
'
.scroll-container
'
).
clientWidth
-
currentDropdownWidth
;
const
offsetMaxWidth
=
this
.
container
.
querySelector
(
'
.scroll-container
'
).
clientWidth
-
currentDropdownWidth
;
if
(
offsetMaxWidth
<
offset
)
{
offset
=
offsetMaxWidth
;
}
...
...
@@ -102,6 +103,7 @@
// Passing glArguments to `new gl[glClass](<arguments>)`
mappingKey
.
reference
=
new
(
Function
.
prototype
.
bind
.
apply
(
gl
[
glClass
],
glArguments
))();
mappingKey
.
reference
.
container
=
this
.
container
;
}
if
(
firstLoad
)
{
...
...
app/assets/javascripts/filtered_search/filtered_search_manager.js
View file @
a9deabea
(()
=>
{
class
FilteredSearchManager
{
constructor
(
page
)
{
this
.
filteredSearchInput
=
document
.
querySelector
(
'
.filtered-search
'
);
this
.
clearSearchButton
=
document
.
querySelector
(
'
.clear-search
'
);
this
.
tokensContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
constructor
(
page
,
container
=
document
)
{
this
.
container
=
container
;
this
.
filteredSearchInput
=
this
.
container
.
querySelector
(
'
.filtered-search
'
);
this
.
clearSearchButton
=
this
.
container
.
querySelector
(
'
.clear-search
'
);
this
.
tokensContainer
=
this
.
container
.
querySelector
(
'
.tokens-container
'
);
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeys
;
if
(
this
.
filteredSearchInput
)
{
this
.
tokenizer
=
gl
.
FilteredSearchTokenizer
;
this
.
dropdownManager
=
new
gl
.
FilteredSearchDropdownManager
(
this
.
filteredSearchInput
.
getAttribute
(
'
data-base-endpoint
'
)
||
''
,
page
);
this
.
dropdownManager
=
new
gl
.
FilteredSearchDropdownManager
(
this
.
filteredSearchInput
.
getAttribute
(
'
data-base-endpoint
'
)
||
''
,
page
,
container
);
this
.
bindEvents
();
this
.
loadSearchParamsFromURL
();
...
...
@@ -132,7 +133,7 @@
}
unselectEditTokens
(
e
)
{
const
inputContainer
=
document
.
querySelector
(
'
.filtered-search-input-container
'
);
const
inputContainer
=
this
.
container
.
querySelector
(
'
.filtered-search-input-container
'
);
const
isElementInFilteredSearch
=
inputContainer
&&
inputContainer
.
contains
(
e
.
target
);
const
isElementInFilterDropdown
=
e
.
target
.
closest
(
'
.filter-dropdown
'
)
!==
null
;
const
isElementTokensContainer
=
e
.
target
.
classList
.
contains
(
'
tokens-container
'
);
...
...
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
View file @
a9deabea
...
...
@@ -41,7 +41,7 @@ class FilteredSearchVisualTokens {
`
;
}
static
addVisualTokenElement
(
name
,
value
,
isSearchTerm
)
{
static
addVisualTokenElement
(
name
,
value
,
isSearchTerm
,
container
)
{
const
li
=
document
.
createElement
(
'
li
'
);
li
.
classList
.
add
(
'
js-visual-token
'
);
li
.
classList
.
add
(
isSearchTerm
?
'
filtered-search-term
'
:
'
filtered-search-token
'
);
...
...
@@ -54,8 +54,8 @@ class FilteredSearchVisualTokens {
}
li
.
querySelector
(
'
.name
'
).
innerText
=
name
;
const
tokensContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
const
input
=
document
.
querySelector
(
'
.filtered-search
'
);
const
tokensContainer
=
container
.
querySelector
(
'
.tokens-container
'
);
const
input
=
container
.
querySelector
(
'
.filtered-search
'
);
tokensContainer
.
insertBefore
(
li
,
input
.
parentElement
);
}
...
...
@@ -71,20 +71,20 @@ class FilteredSearchVisualTokens {
}
}
static
addFilterVisualToken
(
tokenName
,
tokenValue
)
{
static
addFilterVisualToken
(
tokenName
,
tokenValue
,
container
)
{
const
{
lastVisualToken
,
isLastVisualTokenValid
}
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
const
addVisualTokenElement
=
FilteredSearchVisualTokens
.
addVisualTokenElement
;
if
(
isLastVisualTokenValid
)
{
addVisualTokenElement
(
tokenName
,
tokenValue
);
addVisualTokenElement
(
tokenName
,
tokenValue
,
false
,
container
);
}
else
{
const
previousTokenName
=
lastVisualToken
.
querySelector
(
'
.name
'
).
innerText
;
const
tokensContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
const
tokensContainer
=
container
.
querySelector
(
'
.tokens-container
'
);
tokensContainer
.
removeChild
(
lastVisualToken
);
const
value
=
tokenValue
||
tokenName
;
addVisualTokenElement
(
previousTokenName
,
value
);
addVisualTokenElement
(
previousTokenName
,
value
,
false
,
container
);
}
}
...
...
app/assets/stylesheets/pages/boards.scss
View file @
a9deabea
...
...
@@ -420,12 +420,8 @@
display
:
-
webkit-flex
;
display
:
flex
;
.form-control
{
margin-left
:
auto
;
@media
(
min-width
:
$screen-sm-min
)
{
max-width
:
200px
;
}
.issues-filters
{
width
:
100%
;
}
}
...
...
app/views/projects/boards/_show.html.haml
View file @
a9deabea
...
...
@@ -10,6 +10,7 @@
%script
#js-board-template
{
type:
"text/x-template"
}=
render
"projects/boards/components/board"
%script
#js-board-list-template
{
type:
"text/x-template"
}=
render
"projects/boards/components/board_list"
%script
#js-board-modal-filter
{
type:
"text/x-template"
}=
render
"shared/issuable/search_bar"
,
type: :boards_modal
=
render
"projects/issues/head"
...
...
app/views/shared/issuable/_search_bar.html.haml
View file @
a9deabea
-
type
=
local_assigns
.
fetch
(
:type
)
-
block_css_class
=
type
!=
:boards_modal
?
'row-content-block second-block'
:
''
.issues-filters
.issues-details-filters.
row-content-block.second-block.filtered-search-block
.issues-details-filters.
filtered-search-block
{
class:
block_css_class
,
"v-pre"
=>
type
==
:boards_modal
}
=
form_tag
page_filter_path
(
without:
[
:assignee_id
,
:author_id
,
:milestone_title
,
:label_name
,
:search
]),
method: :get
,
class:
'filter-form js-filter-form'
do
-
if
params
[
:search
].
present?
=
hidden_field_tag
:search
,
params
[
:search
]
...
...
@@ -100,7 +101,7 @@
=
render
partial:
"shared/issuable/label_page_create"
=
dropdown_loading
#js-add-issues-btn
.prepend-left-10
-
els
e
-
els
if
type
!=
:boards_modal
=
render
'shared/sort_dropdown'
-
if
@bulk_edit
...
...
@@ -133,19 +134,20 @@
.filter-item.inline.update-issues-btn
=
button_tag
"Update
#{
type
.
to_s
.
humanize
(
capitalize:
false
)
}
"
,
class:
"btn update_selected_issues btn-save"
:javascript
new
UsersSelect
();
new
LabelsSelect
();
new
MilestoneSelect
();
new
IssueStatusSelect
();
new
SubscriptionSelect
();
-
unless
type
===
:boards_modal
:javascript
new
UsersSelect
();
new
LabelsSelect
();
new
MilestoneSelect
();
new
IssueStatusSelect
();
new
SubscriptionSelect
();
$
(
document
).
off
(
'
page:restore
'
).
on
(
'
page:restore
'
,
function
(
event
)
{
if
(
gl
.
FilteredSearchManager
)
{
new
gl
.
FilteredSearchManager
();
}
Issuable
.
init
();
new
gl
.
IssuableBulkActions
({
prefixId
:
'
issue_
'
,
$
(
document
).
off
(
'
page:restore
'
).
on
(
'
page:restore
'
,
function
(
event
)
{
if
(
gl
.
FilteredSearchManager
)
{
new
gl
.
FilteredSearchManager
();
}
Issuable
.
init
();
new
gl
.
IssuableBulkActions
({
prefixId
:
'
issue_
'
,
});
});
});
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