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
Léo-Paul Géneau
gitlab-ce
Commits
024c31fe
Commit
024c31fe
authored
Oct 17, 2018
by
Mike Greiling
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove superfluous IIFEs
parent
f666026d
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
250 additions
and
256 deletions
+250
-256
spec/javascripts/bootstrap_linked_tabs_spec.js
spec/javascripts/bootstrap_linked_tabs_spec.js
+48
-50
spec/javascripts/labels_issue_sidebar_spec.js
spec/javascripts/labels_issue_sidebar_spec.js
+78
-80
spec/javascripts/signin_tabs_memoizer_spec.js
spec/javascripts/signin_tabs_memoizer_spec.js
+124
-126
No files found.
spec/javascripts/bootstrap_linked_tabs_spec.js
View file @
024c31fe
import
LinkedTabs
from
'
~/lib/utils/bootstrap_linked_tabs
'
;
import
LinkedTabs
from
'
~/lib/utils/bootstrap_linked_tabs
'
;
(()
=>
{
describe
(
'
Linked Tabs
'
,
()
=>
{
describe
(
'
Linked Tabs
'
,
()
=>
{
preloadFixtures
(
'
static/linked_tabs.html.raw
'
);
preloadFixtures
(
'
static/linked_tabs.html.raw
'
);
beforeEach
(()
=>
{
loadFixtures
(
'
static/linked_tabs.html.raw
'
);
});
describe
(
'
when is initialized
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
loadFixtures
(
'
static/linked_tabs.html.raw
'
);
spyOn
(
window
.
history
,
'
replaceState
'
).
and
.
callFake
(
function
()
{}
);
});
});
describe
(
'
when is initialized
'
,
()
=>
{
it
(
'
should activate the tab correspondent to the given action
'
,
()
=>
{
beforeEach
(()
=>
{
const
linkedTabs
=
new
LinkedTabs
({
spyOn
(
window
.
history
,
'
replaceState
'
).
and
.
callFake
(
function
()
{});
// eslint-disable-line
action
:
'
tab1
'
,
defaultAction
:
'
tab1
'
,
parentEl
:
'
.linked-tabs
'
,
});
});
it
(
'
should activate the tab correspondent to the given action
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
#tab1
'
).
classList
).
toContain
(
'
active
'
);
const
linkedTabs
=
new
LinkedTabs
({
});
// eslint-disable-line
action
:
'
tab1
'
,
defaultAction
:
'
tab1
'
,
parentEl
:
'
.linked-tabs
'
,
});
expect
(
document
.
querySelector
(
'
#tab1
'
).
classList
).
toContain
(
'
active
'
);
it
(
'
should active the default tab action when the action is show
'
,
()
=>
{
const
linkedTabs
=
new
LinkedTabs
({
// eslint-disable-line
action
:
'
show
'
,
defaultAction
:
'
tab1
'
,
parentEl
:
'
.linked-tabs
'
,
});
});
it
(
'
should active the default tab action when the action is show
'
,
()
=>
{
expect
(
document
.
querySelector
(
'
#tab1
'
).
classList
).
toContain
(
'
active
'
);
const
linkedTabs
=
new
LinkedTabs
({
// eslint-disable-line
action
:
'
show
'
,
defaultAction
:
'
tab1
'
,
parentEl
:
'
.linked-tabs
'
,
});
expect
(
document
.
querySelector
(
'
#tab1
'
).
classList
).
toContain
(
'
active
'
);
});
});
});
});
describe
(
'
on click
'
,
()
=>
{
describe
(
'
on click
'
,
()
=>
{
it
(
'
should change the url according to the clicked tab
'
,
()
=>
{
it
(
'
should change the url according to the clicked tab
'
,
()
=>
{
const
historySpy
=
spyOn
(
window
.
history
,
'
replaceState
'
).
and
.
callFake
(()
=>
{});
const
historySpy
=
spyOn
(
window
.
history
,
'
replaceState
'
).
and
.
callFake
(()
=>
{});
const
linkedTabs
=
new
LinkedTabs
({
const
linkedTabs
=
new
LinkedTabs
({
action
:
'
show
'
,
action
:
'
show
'
,
defaultAction
:
'
tab1
'
,
defaultAction
:
'
tab1
'
,
parentEl
:
'
.linked-tabs
'
,
parentEl
:
'
.linked-tabs
'
,
});
});
const
secondTab
=
document
.
querySelector
(
'
.linked-tabs li:nth-child(2) a
'
);
const
secondTab
=
document
.
querySelector
(
'
.linked-tabs li:nth-child(2) a
'
);
const
newState
=
const
newState
=
secondTab
.
getAttribute
(
'
href
'
)
+
secondTab
.
getAttribute
(
'
href
'
)
+
linkedTabs
.
currentLocation
.
search
+
linkedTabs
.
currentLocation
.
search
+
linkedTabs
.
currentLocation
.
hash
;
linkedTabs
.
currentLocation
.
hash
;
secondTab
.
click
();
secondTab
.
click
();
if
(
historySpy
)
{
if
(
historySpy
)
{
expect
(
historySpy
).
toHaveBeenCalledWith
(
expect
(
historySpy
).
toHaveBeenCalledWith
(
{
{
url
:
newState
,
url
:
newState
,
},
},
document
.
title
,
document
.
title
,
newState
,
newState
,
);
);
}
}
});
});
});
});
});
})
()
;
});
spec/javascripts/labels_issue_sidebar_spec.js
View file @
024c31fe
...
@@ -12,108 +12,106 @@ import '~/api';
...
@@ -12,108 +12,106 @@ import '~/api';
import
'
~/create_label
'
;
import
'
~/create_label
'
;
import
'
~/users_select
'
;
import
'
~/users_select
'
;
(()
=>
{
let
saveLabelCount
=
0
;
let
saveLabelCount
=
0
;
let
mock
;
let
mock
;
describe
(
'
Issue dropdown sidebar
'
,
()
=>
{
describe
(
'
Issue dropdown sidebar
'
,
()
=>
{
preloadFixtures
(
'
static/issue_sidebar_label.html.raw
'
);
preloadFixtures
(
'
static/issue_sidebar_label.html.raw
'
);
beforeEach
(()
=>
{
beforeEach
(()
=>
{
loadFixtures
(
'
static/issue_sidebar_label.html.raw
'
);
loadFixtures
(
'
static/issue_sidebar_label.html.raw
'
);
mock
=
new
MockAdapter
(
axios
);
mock
=
new
MockAdapter
(
axios
);
new
IssuableContext
(
'
{"id":1,"name":"Administrator","username":"root"}
'
);
new
IssuableContext
(
'
{"id":1,"name":"Administrator","username":"root"}
'
);
new
LabelsSelect
();
new
LabelsSelect
();
mock
.
onGet
(
'
/root/test/labels.json
'
).
reply
(()
=>
{
mock
.
onGet
(
'
/root/test/labels.json
'
).
reply
(()
=>
{
const
labels
=
Array
(
10
)
const
labels
=
Array
(
10
)
.
fill
()
.
fill
()
.
map
((
_
,
i
)
=>
({
.
map
((
_
,
i
)
=>
({
id
:
i
,
id
:
i
,
title
:
`test
${
i
}
`
,
title
:
`test
${
i
}
`
,
color
:
'
#5CB85C
'
,
color
:
'
#5CB85C
'
,
}));
}));
return
[
200
,
labels
];
return
[
200
,
labels
];
});
});
mock
.
onPut
(
'
/root/test/issues/2.json
'
).
reply
(()
=>
{
mock
.
onPut
(
'
/root/test/issues/2.json
'
).
reply
(()
=>
{
const
labels
=
Array
(
saveLabelCount
)
const
labels
=
Array
(
saveLabelCount
)
.
fill
()
.
fill
()
.
map
((
_
,
i
)
=>
({
.
map
((
_
,
i
)
=>
({
id
:
i
,
id
:
i
,
title
:
`test
${
i
}
`
,
title
:
`test
${
i
}
`
,
color
:
'
#5CB85C
'
,
color
:
'
#5CB85C
'
,
}));
}));
return
[
200
,
{
labels
}];
return
[
200
,
{
labels
}];
});
});
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
mock
.
restore
();
mock
.
restore
();
});
});
it
(
'
changes collapsed tooltip when changing labels when less than 5
'
,
done
=>
{
saveLabelCount
=
5
;
$
(
'
.edit-link
'
)
.
get
(
0
)
.
click
();
setTimeout
(()
=>
{
expect
(
$
(
'
.dropdown-content a
'
).
length
).
toBe
(
10
);
$
(
'
.dropdown-content a
'
).
each
(
function
(
i
)
{
if
(
i
<
saveLabelCount
)
{
$
(
this
)
.
get
(
0
)
.
click
();
}
});
it
(
'
changes collapsed tooltip when changing labels when less than 5
'
,
done
=>
{
saveLabelCount
=
5
;
$
(
'
.edit-link
'
)
$
(
'
.edit-link
'
)
.
get
(
0
)
.
get
(
0
)
.
click
();
.
click
();
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
$
(
'
.dropdown-content a
'
).
length
).
toBe
(
10
);
expect
(
$
(
'
.sidebar-collapsed-icon
'
).
attr
(
'
data-original-title
'
)).
toBe
(
'
test 0, test 1, test 2, test 3, test 4
'
,
$
(
'
.dropdown-content a
'
).
each
(
function
(
i
)
{
);
if
(
i
<
saveLabelCount
)
{
done
();
$
(
this
)
.
get
(
0
)
.
click
();
}
});
$
(
'
.edit-link
'
)
.
get
(
0
)
.
click
();
setTimeout
(()
=>
{
expect
(
$
(
'
.sidebar-collapsed-icon
'
).
attr
(
'
data-original-title
'
)).
toBe
(
'
test 0, test 1, test 2, test 3, test 4
'
,
);
done
();
},
0
);
},
0
);
},
0
);
});
},
0
);
});
it
(
'
changes collapsed tooltip when changing labels when more than 5
'
,
done
=>
{
saveLabelCount
=
6
;
$
(
'
.edit-link
'
)
.
get
(
0
)
.
click
();
setTimeout
(()
=>
{
expect
(
$
(
'
.dropdown-content a
'
).
length
).
toBe
(
10
);
$
(
'
.dropdown-content a
'
).
each
(
function
(
i
)
{
if
(
i
<
saveLabelCount
)
{
$
(
this
)
.
get
(
0
)
.
click
();
}
});
it
(
'
changes collapsed tooltip when changing labels when more than 5
'
,
done
=>
{
saveLabelCount
=
6
;
$
(
'
.edit-link
'
)
$
(
'
.edit-link
'
)
.
get
(
0
)
.
get
(
0
)
.
click
();
.
click
();
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
$
(
'
.dropdown-content a
'
).
length
).
toBe
(
10
);
expect
(
$
(
'
.sidebar-collapsed-icon
'
).
attr
(
'
data-original-title
'
)).
toBe
(
'
test 0, test 1, test 2, test 3, test 4, and 1 more
'
,
$
(
'
.dropdown-content a
'
).
each
(
function
(
i
)
{
);
if
(
i
<
saveLabelCount
)
{
done
();
$
(
this
)
.
get
(
0
)
.
click
();
}
});
$
(
'
.edit-link
'
)
.
get
(
0
)
.
click
();
setTimeout
(()
=>
{
expect
(
$
(
'
.sidebar-collapsed-icon
'
).
attr
(
'
data-original-title
'
)).
toBe
(
'
test 0, test 1, test 2, test 3, test 4, and 1 more
'
,
);
done
();
},
0
);
},
0
);
},
0
);
});
}
,
0
);
});
});
})
()
;
});
spec/javascripts/signin_tabs_memoizer_spec.js
View file @
024c31fe
import
AccessorUtilities
from
'
~/lib/utils/accessor
'
;
import
AccessorUtilities
from
'
~/lib/utils/accessor
'
;
import
SigninTabsMemoizer
from
'
~/pages/sessions/new/signin_tabs_memoizer
'
;
import
SigninTabsMemoizer
from
'
~/pages/sessions/new/signin_tabs_memoizer
'
;
(()
=>
{
describe
(
'
SigninTabsMemoizer
'
,
()
=>
{
describe
(
'
SigninTabsMemoizer
'
,
()
=>
{
const
fixtureTemplate
=
'
static/signin_tabs.html.raw
'
;
const
fixtureTemplate
=
'
static/signin_tabs.html.raw
'
;
const
tabSelector
=
'
ul.new-session-tabs
'
;
const
tabSelector
=
'
ul.new-session-tabs
'
;
const
currentTabKey
=
'
current_signin_tab
'
;
const
currentTabKey
=
'
current_signin_tab
'
;
let
memo
;
let
memo
;
function
createMemoizer
()
{
function
createMemoizer
()
{
memo
=
new
SigninTabsMemoizer
({
memo
=
new
SigninTabsMemoizer
({
currentTabKey
,
currentTabKey
,
tabSelector
,
tabSelector
,
});
});
return
memo
;
return
memo
;
}
}
preloadFixtures
(
fixtureTemplate
);
preloadFixtures
(
fixtureTemplate
);
beforeEach
(()
=>
{
beforeEach
(()
=>
{
loadFixtures
(
fixtureTemplate
);
loadFixtures
(
fixtureTemplate
);
spyOn
(
AccessorUtilities
,
'
isLocalStorageAccessSafe
'
).
and
.
returnValue
(
true
);
spyOn
(
AccessorUtilities
,
'
isLocalStorageAccessSafe
'
).
and
.
returnValue
(
true
);
});
});
it
(
'
does nothing if no tab was previously selected
'
,
()
=>
{
it
(
'
does nothing if no tab was previously selected
'
,
()
=>
{
createMemoizer
();
createMemoizer
();
expect
(
document
.
querySelector
(
`
${
tabSelector
}
> li.active a`
).
getAttribute
(
'
href
'
)).
toEqual
(
expect
(
document
.
querySelector
(
`
${
tabSelector
}
> li.active a`
).
getAttribute
(
'
href
'
)).
toEqual
(
'
#ldap
'
,
'
#ldap
'
,
);
);
});
});
it
(
'
shows last selected tab on boot
'
,
()
=>
{
it
(
'
shows last selected tab on boot
'
,
()
=>
{
createMemoizer
().
saveData
(
'
#ldap
'
);
createMemoizer
().
saveData
(
'
#ldap
'
);
const
fakeTab
=
{
const
fakeTab
=
{
click
:
()
=>
{},
click
:
()
=>
{},
};
};
spyOn
(
document
,
'
querySelector
'
).
and
.
returnValue
(
fakeTab
);
spyOn
(
document
,
'
querySelector
'
).
and
.
returnValue
(
fakeTab
);
spyOn
(
fakeTab
,
'
click
'
);
spyOn
(
fakeTab
,
'
click
'
);
memo
.
bootstrap
();
memo
.
bootstrap
();
// verify that triggers click on the last selected tab
// verify that triggers click on the last selected tab
expect
(
document
.
querySelector
).
toHaveBeenCalledWith
(
`
${
tabSelector
}
a[href="#ldap"]`
);
expect
(
document
.
querySelector
).
toHaveBeenCalledWith
(
`
${
tabSelector
}
a[href="#ldap"]`
);
expect
(
fakeTab
.
click
).
toHaveBeenCalled
();
expect
(
fakeTab
.
click
).
toHaveBeenCalled
();
});
});
it
(
'
clicks the first tab if value in local storage is bad
'
,
()
=>
{
it
(
'
clicks the first tab if value in local storage is bad
'
,
()
=>
{
createMemoizer
().
saveData
(
'
#bogus
'
);
createMemoizer
().
saveData
(
'
#bogus
'
);
const
fakeTab
=
{
const
fakeTab
=
{
click
:
()
=>
{},
click
:
()
=>
{},
};
};
spyOn
(
document
,
'
querySelector
'
).
and
.
callFake
(
spyOn
(
document
,
'
querySelector
'
).
and
.
callFake
(
selector
=>
(
selector
===
`
${
tabSelector
}
a[href="#bogus"]`
?
null
:
fakeTab
),
selector
=>
(
selector
===
`
${
tabSelector
}
a[href="#bogus"]`
?
null
:
fakeTab
),
);
);
spyOn
(
fakeTab
,
'
click
'
);
spyOn
(
fakeTab
,
'
click
'
);
memo
.
bootstrap
();
memo
.
bootstrap
();
// verify that triggers click on stored selector and fallback
// verify that triggers click on stored selector and fallback
expect
(
document
.
querySelector
.
calls
.
allArgs
()).
toEqual
([
expect
(
document
.
querySelector
.
calls
.
allArgs
()).
toEqual
([
[
'
ul.new-session-tabs a[href="#bogus"]
'
],
[
'
ul.new-session-tabs a[href="#bogus"]
'
],
[
'
ul.new-session-tabs a
'
],
[
'
ul.new-session-tabs a
'
],
]);
]);
expect
(
fakeTab
.
click
).
toHaveBeenCalled
();
expect
(
fakeTab
.
click
).
toHaveBeenCalled
();
});
});
it
(
'
saves last selected tab on change
'
,
()
=>
{
it
(
'
saves last selected tab on change
'
,
()
=>
{
createMemoizer
();
createMemoizer
();
document
.
querySelector
(
'
a[href="#login-pane"]
'
).
click
();
document
.
querySelector
(
'
a[href="#login-pane"]
'
).
click
();
expect
(
memo
.
readData
()).
toEqual
(
'
#login-pane
'
);
expect
(
memo
.
readData
()).
toEqual
(
'
#login-pane
'
);
});
});
it
(
'
overrides last selected tab with hash tag when given
'
,
()
=>
{
it
(
'
overrides last selected tab with hash tag when given
'
,
()
=>
{
window
.
location
.
hash
=
'
#ldap
'
;
window
.
location
.
hash
=
'
#ldap
'
;
createMemoizer
();
createMemoizer
();
expect
(
memo
.
readData
()).
toEqual
(
'
#ldap
'
);
expect
(
memo
.
readData
()).
toEqual
(
'
#ldap
'
);
});
});
describe
(
'
class constructor
'
,
()
=>
{
describe
(
'
class constructor
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
memo
=
createMemoizer
();
memo
=
createMemoizer
();
});
});
it
(
'
should set .isLocalStorageAvailable
'
,
()
=>
{
it
(
'
should set .isLocalStorageAvailable
'
,
()
=>
{
expect
(
AccessorUtilities
.
isLocalStorageAccessSafe
).
toHaveBeenCalled
();
expect
(
AccessorUtilities
.
isLocalStorageAccessSafe
).
toHaveBeenCalled
();
expect
(
memo
.
isLocalStorageAvailable
).
toBe
(
true
);
expect
(
memo
.
isLocalStorageAvailable
).
toBe
(
true
);
});
});
});
});
describe
(
'
saveData
'
,
()
=>
{
describe
(
'
saveData
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
memo
=
{
memo
=
{
currentTabKey
,
currentTabKey
,
};
};
spyOn
(
localStorage
,
'
setItem
'
);
spyOn
(
localStorage
,
'
setItem
'
);
});
});
describe
(
'
if .isLocalStorageAvailable is `false`
'
,
()
=>
{
describe
(
'
if .isLocalStorageAvailable is `false`
'
,
()
=>
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
memo
.
isLocalStorageAvailable
=
false
;
memo
.
isLocalStorageAvailable
=
false
;
SigninTabsMemoizer
.
prototype
.
saveData
.
call
(
memo
);
SigninTabsMemoizer
.
prototype
.
saveData
.
call
(
memo
);
});
});
it
(
'
should not call .setItem
'
,
()
=>
{
it
(
'
should not call .setItem
'
,
()
=>
{
expect
(
localStorage
.
setItem
).
not
.
toHaveBeenCalled
();
expect
(
localStorage
.
setItem
).
not
.
toHaveBeenCalled
();
});
});
});
});
describe
(
'
if .isLocalStorageAvailable is `true`
'
,
()
=>
{
describe
(
'
if .isLocalStorageAvailable is `true`
'
,
()
=>
{
const
value
=
'
value
'
;
const
value
=
'
value
'
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
memo
.
isLocalStorageAvailable
=
true
;
memo
.
isLocalStorageAvailable
=
true
;
SigninTabsMemoizer
.
prototype
.
saveData
.
call
(
memo
,
value
);
SigninTabsMemoizer
.
prototype
.
saveData
.
call
(
memo
,
value
);
});
});
it
(
'
should call .setItem
'
,
()
=>
{
it
(
'
should call .setItem
'
,
()
=>
{
expect
(
localStorage
.
setItem
).
toHaveBeenCalledWith
(
currentTabKey
,
value
);
expect
(
localStorage
.
setItem
).
toHaveBeenCalledWith
(
currentTabKey
,
value
);
});
});
});
});
});
});
describe
(
'
readData
'
,
()
=>
{
describe
(
'
readData
'
,
()
=>
{
const
itemValue
=
'
itemValue
'
;
const
itemValue
=
'
itemValue
'
;
let
readData
;
let
readData
;
beforeEach
(()
=>
{
beforeEach
(()
=>
{
memo
=
{
memo
=
{
currentTabKey
,
currentTabKey
,
};
};
spyOn
(
localStorage
,
'
getItem
'
).
and
.
returnValue
(
itemValue
);
spyOn
(
localStorage
,
'
getItem
'
).
and
.
returnValue
(
itemValue
);
});
});
describe
(
'
if .isLocalStorageAvailable is `false`
'
,
()
=>
{
describe
(
'
if .isLocalStorageAvailable is `false`
'
,
()
=>
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
memo
.
isLocalStorageAvailable
=
false
;
memo
.
isLocalStorageAvailable
=
false
;
readData
=
SigninTabsMemoizer
.
prototype
.
readData
.
call
(
memo
);
readData
=
SigninTabsMemoizer
.
prototype
.
readData
.
call
(
memo
);
});
});
it
(
'
should not call .getItem and should return `null`
'
,
()
=>
{
it
(
'
should not call .getItem and should return `null`
'
,
()
=>
{
expect
(
localStorage
.
getItem
).
not
.
toHaveBeenCalled
();
expect
(
localStorage
.
getItem
).
not
.
toHaveBeenCalled
();
expect
(
readData
).
toBe
(
null
);
expect
(
readData
).
toBe
(
null
);
});
});
});
});
describe
(
'
if .isLocalStorageAvailable is `true`
'
,
()
=>
{
describe
(
'
if .isLocalStorageAvailable is `true`
'
,
()
=>
{
beforeEach
(
function
()
{
beforeEach
(
function
()
{
memo
.
isLocalStorageAvailable
=
true
;
memo
.
isLocalStorageAvailable
=
true
;
readData
=
SigninTabsMemoizer
.
prototype
.
readData
.
call
(
memo
);
readData
=
SigninTabsMemoizer
.
prototype
.
readData
.
call
(
memo
);
});
});
it
(
'
should call .getItem and return the localStorage value
'
,
()
=>
{
it
(
'
should call .getItem and return the localStorage value
'
,
()
=>
{
expect
(
window
.
localStorage
.
getItem
).
toHaveBeenCalledWith
(
currentTabKey
);
expect
(
window
.
localStorage
.
getItem
).
toHaveBeenCalledWith
(
currentTabKey
);
expect
(
readData
).
toBe
(
itemValue
);
expect
(
readData
).
toBe
(
itemValue
);
});
});
});
});
});
});
});
})
()
;
});
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