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
Boxiang Sun
gitlab-ce
Commits
695caab0
Commit
695caab0
authored
Aug 15, 2017
by
Jacob Schatz
Committed by
Jarka Kadlecova
Aug 16, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merge branch 'bpj-repo-editor-fixes' into 'master'
Repo Editor Fixes See merge request !13468
parent
7753a0e0
Changes
19
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
141 additions
and
114 deletions
+141
-114
app/assets/javascripts/project.js
app/assets/javascripts/project.js
+1
-1
app/assets/javascripts/repo/components/repo_commit_section.vue
...ssets/javascripts/repo/components/repo_commit_section.vue
+1
-3
app/assets/javascripts/repo/components/repo_edit_button.vue
app/assets/javascripts/repo/components/repo_edit_button.vue
+10
-8
app/assets/javascripts/repo/components/repo_editor.vue
app/assets/javascripts/repo/components/repo_editor.vue
+6
-29
app/assets/javascripts/repo/components/repo_sidebar.vue
app/assets/javascripts/repo/components/repo_sidebar.vue
+2
-4
app/assets/javascripts/repo/components/repo_tab.vue
app/assets/javascripts/repo/components/repo_tab.vue
+3
-3
app/assets/javascripts/repo/components/repo_tabs.vue
app/assets/javascripts/repo/components/repo_tabs.vue
+10
-5
app/assets/javascripts/repo/helpers/repo_helper.js
app/assets/javascripts/repo/helpers/repo_helper.js
+2
-2
app/assets/javascripts/repo/index.js
app/assets/javascripts/repo/index.js
+3
-0
app/assets/javascripts/repo/services/repo_service.js
app/assets/javascripts/repo/services/repo_service.js
+6
-4
app/assets/javascripts/repo/stores/repo_store.js
app/assets/javascripts/repo/stores/repo_store.js
+1
-1
app/assets/javascripts/vue_shared/components/popup_dialog.vue
...assets/javascripts/vue_shared/components/popup_dialog.vue
+46
-23
app/assets/stylesheets/pages/repo.scss
app/assets/stylesheets/pages/repo.scss
+1
-7
app/views/shared/repo/_repo.html.haml
app/views/shared/repo/_repo.html.haml
+0
-1
spec/javascripts/repo/components/repo_edit_button_spec.js
spec/javascripts/repo/components/repo_edit_button_spec.js
+2
-0
spec/javascripts/repo/components/repo_editor_spec.js
spec/javascripts/repo/components/repo_editor_spec.js
+37
-14
spec/javascripts/repo/components/repo_file_buttons_spec.js
spec/javascripts/repo/components/repo_file_buttons_spec.js
+1
-0
spec/javascripts/repo/components/repo_tab_spec.js
spec/javascripts/repo/components/repo_tab_spec.js
+7
-7
spec/javascripts/repo/components/repo_tabs_spec.js
spec/javascripts/repo/components/repo_tabs_spec.js
+2
-2
No files found.
app/assets/javascripts/project.js
View file @
695caab0
...
...
@@ -130,7 +130,7 @@ import Cookies from 'js-cookie';
var
action
=
$form
.
attr
(
'
action
'
);
var
divider
=
action
.
indexOf
(
'
?
'
)
===
-
1
?
'
?
'
:
'
&
'
;
if
(
shouldVisit
)
{
gl
.
utils
.
visitUrl
(
action
+
''
+
divider
+
''
+
$form
.
serialize
()
);
gl
.
utils
.
visitUrl
(
`
${
action
}${
divider
}${
$form
.
serialize
()}
`
);
}
}
}
...
...
app/assets/javascripts/repo/components/repo_commit_section.vue
View file @
695caab0
...
...
@@ -5,7 +5,7 @@ import RepoMixin from '../mixins/repo_mixin';
import
Helper
from
'
../helpers/repo_helper
'
;
import
Service
from
'
../services/repo_service
'
;
const
RepoCommitSection
=
{
export
default
{
data
:
()
=>
Store
,
mixins
:
[
RepoMixin
],
...
...
@@ -54,8 +54,6 @@ const RepoCommitSection = {
},
},
};
export
default
RepoCommitSection
;
</
script
>
<
template
>
...
...
app/assets/javascripts/repo/components/repo_edit_button.vue
View file @
695caab0
...
...
@@ -23,19 +23,21 @@ export default {
this
.
editMode
=
!
this
.
editMode
;
Store
.
toggleBlobView
();
},
},
watch
:
{
editMode
()
{
toggleProjectRefsForm
()
{
if
(
this
.
editMode
)
{
$
(
'
.project-refs-form
'
).
addClass
(
'
disabled
'
);
$
(
'
.
js-tree-ref-target-holder
'
).
show
();
$
(
'
.project-refs-form
'
).
addClass
(
'
disabled
-content
'
);
$
(
'
.
project-refs-target-form
'
).
show
();
}
else
{
$
(
'
.project-refs-form
'
).
removeClass
(
'
disabled
'
);
$
(
'
.
js-tree-ref-target-holder
'
).
hide
();
$
(
'
.project-refs-form
'
).
removeClass
(
'
disabled
-content
'
);
$
(
'
.
project-refs-target-form
'
).
hide
();
}
},
},
watch
:
{
editMode
()
{
this
.
toggleProjectRefsForm
();
},
},
};
</
script
>
...
...
app/assets/javascripts/repo/components/repo_editor.vue
View file @
695caab0
...
...
@@ -32,7 +32,6 @@ const RepoEditor = {
const
languages
=
this
.
monaco
.
languages
.
getLanguages
();
const
languageID
=
Helper
.
getLanguageIDForFile
(
this
.
activeFile
,
languages
);
this
.
showHide
();
const
newModel
=
this
.
monaco
.
editor
.
createModel
(
this
.
blobRaw
,
languageID
);
this
.
monacoInstance
.
setModel
(
newModel
);
...
...
@@ -40,14 +39,6 @@ const RepoEditor = {
},
methods
:
{
showHide
()
{
if
(
!
this
.
openedFiles
.
length
||
(
this
.
binary
&&
!
this
.
activeFile
.
raw
))
{
this
.
$el
.
style
.
display
=
'
none
'
;
}
else
{
this
.
$el
.
style
.
display
=
'
inline-block
'
;
}
},
addMonacoEvents
()
{
this
.
monacoInstance
.
onMouseUp
(
this
.
onMonacoEditorMouseUp
);
this
.
monacoInstance
.
onKeyUp
(
this
.
onMonacoEditorKeysPressed
.
bind
(
this
));
...
...
@@ -73,11 +64,6 @@ const RepoEditor = {
column
:
1
,
});
},
activeFileLabel
()
{
this
.
showHide
();
},
dialog
:
{
handler
(
obj
)
{
const
newObj
=
obj
;
...
...
@@ -99,21 +85,7 @@ const RepoEditor = {
deep
:
true
,
},
isTree
()
{
this
.
showHide
();
},
openedFiles
()
{
this
.
showHide
();
},
binary
()
{
this
.
showHide
();
},
blobRaw
()
{
this
.
showHide
();
if
(
this
.
isTree
)
return
;
this
.
monacoInstance
.
setModel
(
null
);
...
...
@@ -125,11 +97,16 @@ const RepoEditor = {
this
.
monacoInstance
.
setModel
(
newModel
);
},
},
computed
:
{
shouldHideEditor
()
{
return
!
this
.
openedFiles
.
length
||
(
this
.
binary
&&
!
this
.
activeFile
.
raw
);
},
},
};
export
default
RepoEditor
;
</
script
>
<
template
>
<div
id=
"ide"
></div>
<div
id=
"ide"
v-if=
'!shouldHideEditor'
></div>
</
template
>
app/assets/javascripts/repo/components/repo_sidebar.vue
View file @
695caab0
...
...
@@ -8,7 +8,7 @@ import RepoFile from './repo_file.vue';
import
RepoLoadingFile
from
'
./repo_loading_file.vue
'
;
import
RepoMixin
from
'
../mixins/repo_mixin
'
;
const
RepoSidebar
=
{
export
default
{
mixins
:
[
RepoMixin
],
components
:
{
'
repo-file-options
'
:
RepoFileOptions
,
...
...
@@ -59,12 +59,10 @@ const RepoSidebar = {
},
},
};
export
default
RepoSidebar
;
</
script
>
<
template
>
<div
id=
"sidebar"
:class=
"
{'sidebar-mini' : isMini}"
v-cloak
>
<div
id=
"sidebar"
:class=
"
{'sidebar-mini' : isMini}">
<table
class=
"table"
>
<thead
v-if=
"!isMini"
>
<tr>
...
...
app/assets/javascripts/repo/components/repo_tab.vue
View file @
695caab0
...
...
@@ -28,9 +28,9 @@ const RepoTab = {
methods
:
{
tabClicked
:
Store
.
setActiveFiles
,
xClicked
(
file
)
{
closeTab
(
file
)
{
if
(
file
.
changed
)
return
;
this
.
$emit
(
'
xclick
ed
'
,
file
);
this
.
$emit
(
'
tabclos
ed
'
,
file
);
},
},
};
...
...
@@ -43,7 +43,7 @@ export default RepoTab;
<a
href=
"#0"
class=
"close"
@
click.prevent=
"
xClicked
(tab)"
@
click.prevent=
"
closeTab
(tab)"
:aria-label=
"closeLabel"
>
<i
class=
"fa"
...
...
app/assets/javascripts/repo/components/repo_tabs.vue
View file @
695caab0
...
...
@@ -13,7 +13,7 @@ const RepoTabs = {
data
:
()
=>
Store
,
methods
:
{
xClick
ed
(
file
)
{
tabClos
ed
(
file
)
{
Store
.
removeFromOpenedFiles
(
file
);
},
},
...
...
@@ -23,10 +23,15 @@ export default RepoTabs;
</
script
>
<
template
>
<ul
v-if=
"isMini"
id=
"tabs"
>
<repo-tab
v-for=
"tab in openedFiles"
:key=
"tab.id"
:tab=
"tab"
:class=
"
{'active' : tab.active}" @xclicked="xClicked"/>
<ul
id=
"tabs"
v-if=
"isMini"
>
<repo-tab
v-for=
"tab in openedFiles"
:key=
"tab.id"
:tab=
"tab"
:class=
"
{'active' : tab.active}"
@tabclosed="tabClosed"
/>
<li
class=
"tabs-divider"
/>
</ul>
</
template
>
app/assets/javascripts/repo/helpers/repo_helper.js
View file @
695caab0
...
...
@@ -62,7 +62,7 @@ const RepoHelper = {
file
.
opened
=
true
;
file
.
icon
=
'
fa-folder-open
'
;
RepoHelper
.
toURL
(
file
.
url
,
file
.
name
);
RepoHelper
.
updateHistoryEntry
(
file
.
url
,
file
.
name
);
return
file
;
},
...
...
@@ -276,7 +276,7 @@ const RepoHelper = {
RepoHelper
.
key
=
key
;
},
toURL
(
url
,
title
)
{
updateHistoryEntry
(
url
,
title
)
{
const
history
=
window
.
history
;
RepoHelper
.
key
=
RepoHelper
.
genKey
();
...
...
app/assets/javascripts/repo/index.js
View file @
695caab0
...
...
@@ -43,6 +43,9 @@ function initRepo(el) {
components
:
{
repo
:
Repo
,
},
render
(
createElement
)
{
return
createElement
(
'
repo
'
);
},
});
}
...
...
app/assets/javascripts/repo/services/repo_service.js
View file @
695caab0
...
...
@@ -15,10 +15,12 @@ const RepoService = {
checkCurrentBranchIsCommitable
()
{
const
url
=
Store
.
service
.
refsUrl
;
return
axios
.
get
(
url
,
{
params
:
{
ref
:
Store
.
currentBranch
,
search
:
Store
.
currentBranch
,
}
});
return
axios
.
get
(
url
,
{
params
:
{
ref
:
Store
.
currentBranch
,
search
:
Store
.
currentBranch
,
},
});
},
getRaw
(
url
)
{
...
...
app/assets/javascripts/repo/stores/repo_store.js
View file @
695caab0
...
...
@@ -90,7 +90,7 @@ const RepoStore = {
}).
catch
(
Helper
.
loadingError
);
}
if
(
!
file
.
loading
)
Helper
.
toURL
(
file
.
url
,
file
.
name
);
if
(
!
file
.
loading
)
Helper
.
updateHistoryEntry
(
file
.
url
,
file
.
name
);
RepoStore
.
binary
=
file
.
binary
;
},
...
...
app/assets/javascripts/vue_shared/components/popup_dialog.vue
View file @
695caab0
<
script
>
const
PopupDialog
=
{
export
default
{
name
:
'
popup-dialog
'
,
props
:
{
open
:
Boolean
,
title
:
String
,
body
:
String
,
open
:
{
type
:
Boolean
,
required
:
true
,
},
title
:
{
type
:
String
,
required
:
true
,
},
body
:
{
type
:
String
,
required
:
true
,
},
kind
:
{
type
:
String
,
required
:
false
,
default
:
'
primary
'
,
},
closeButtonLabel
:
{
type
:
String
,
required
:
false
,
default
:
'
Cancel
'
,
},
primaryButtonLabel
:
{
type
:
String
,
default
:
'
Save changes
'
,
required
:
true
,
},
},
computed
:
{
typeOfClass
()
{
const
className
=
`btn-
${
this
.
kind
}
`
;
const
returnObj
=
{};
returnObj
[
className
]
=
true
;
return
returnObj
;
btnKindClass
()
{
return
{
[
`btn-
${
this
.
kind
}
`
]:
true
,
};
},
},
...
...
@@ -33,33 +43,46 @@ const PopupDialog = {
close
()
{
this
.
$emit
(
'
toggle
'
,
false
);
},
yesClick
()
{
this
.
$emit
(
'
submit
'
,
true
);
},
noClick
()
{
this
.
$emit
(
'
submit
'
,
false
);
emitSubmit
(
status
)
{
this
.
$emit
(
'
submit
'
,
status
);
},
},
};
export
default
PopupDialog
;
</
script
>
<
template
>
<div
class=
"modal popup-dialog"
tabindex=
"-1"
v-show=
"open"
role=
"dialog"
>
<div
class=
"modal popup-dialog"
v-if=
"open"
role=
"dialog"
tabindex=
"-1"
>
<div
class=
"modal-dialog"
role=
"document"
>
<div
class=
"modal-content"
>
<div
class=
"modal-header"
>
<button
type=
"button"
class=
"close"
@
click=
"close"
data-dismiss=
"modal"
aria-label=
"Close"
><span
aria-hidden=
"true"
>
×
</span></button>
<button
type=
"button"
class=
"close"
@
click=
"close"
aria-label=
"Close"
>
<span
aria-hidden=
"true"
>
×
</span>
</button>
<h4
class=
"modal-title"
>
{{
this
.
title
}}
</h4>
</div>
<div
class=
"modal-body"
>
<p>
{{
this
.
body
}}
</p>
</div>
<div
class=
"modal-footer"
>
<button
type=
"button"
class=
"btn btn-default"
data-dismiss=
"modal"
@
click=
"noClick"
>
{{
closeButtonLabel
}}
</button>
<button
type=
"button"
class=
"btn"
:class=
"typeOfClass"
@
click=
"yesClick"
>
{{
primaryButtonLabel
}}
</button>
<button
type=
"button"
class=
"btn btn-default"
@
click=
"emitSubmit(false)"
>
{{
closeButtonLabel
}}
</button>
<button
type=
"button"
class=
"btn"
:class=
"btnKindClass"
@
click=
"emitSubmit(true)"
>
{{
primaryButtonLabel
}}
</button>
</div>
</div>
</div>
...
...
app/assets/stylesheets/pages/repo.scss
View file @
695caab0
...
...
@@ -28,11 +28,6 @@
.project-refs-form
,
.project-refs-target-form
{
display
:
inline-block
;
&
.disabled
{
opacity
:
0
.5
;
pointer-events
:
none
;
}
}
.fade-enter
,
...
...
@@ -133,7 +128,6 @@
a
{
@include
str-truncated
(
100px
);
color
:
$black
;
display
:
inline-block
;
width
:
100px
;
text-align
:
center
;
vertical-align
:
middle
;
...
...
@@ -298,7 +292,7 @@
}
.fa
{
font-size
:
$code_font_size
;
font-size
:
12px
;
margin-right
:
5px
;
}
}
...
...
app/views/shared/repo/_repo.html.haml
View file @
695caab0
#repo
{
data:
{
url:
content_url
,
project_name:
project
.
name
,
refs_url:
refs_project_path
(
project
,
format: :json
),
project_url:
project_path
(
project
),
project_id:
project
.
id
,
can_commit:
(
!!
can_push_branch?
(
project
,
@ref
)).
to_s
}
}
%repo
spec/javascripts/repo/components/repo_edit_button_spec.js
View file @
695caab0
...
...
@@ -19,11 +19,13 @@ describe('RepoEditButton', () => {
expect
(
vm
.
$el
.
textContent
).
toMatch
(
'
Edit
'
);
spyOn
(
vm
,
'
editClicked
'
).
and
.
callThrough
();
spyOn
(
vm
,
'
toggleProjectRefsForm
'
);
vm
.
$el
.
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
editClicked
).
toHaveBeenCalled
();
expect
(
vm
.
toggleProjectRefsForm
).
toHaveBeenCalled
();
expect
(
vm
.
$el
.
textContent
).
toMatch
(
'
Cancel edit
'
);
done
();
});
...
...
spec/javascripts/repo/components/repo_editor_spec.js
View file @
695caab0
import
Vue
from
'
vue
'
;
import
repoEditor
from
'
~/repo/components/repo_editor.vue
'
;
import
RepoStore
from
'
~/repo/stores/repo_store
'
;
describe
(
'
RepoEditor
'
,
()
=>
{
function
createComponent
()
{
beforeEach
(()
=>
{
const
RepoEditor
=
Vue
.
extend
(
repoEditor
);
return
new
RepoEditor
().
$mount
();
}
this
.
vm
=
new
RepoEditor
().
$mount
();
});
it
(
'
renders an ide container
'
,
(
done
)
=>
{
this
.
vm
.
openedFiles
=
[
'
idiidid
'
];
this
.
vm
.
binary
=
false
;
it
(
'
renders an ide container
'
,
()
=>
{
const
monacoInstance
=
jasmine
.
createSpyObj
(
'
monacoInstance
'
,
[
'
onMouseUp
'
,
'
onKeyUp
'
,
'
setModel
'
,
'
updateOptions
'
]);
const
monaco
=
{
editor
:
jasmine
.
createSpyObj
(
'
editor
'
,
[
'
create
'
]),
};
RepoStore
.
monaco
=
monaco
;
Vue
.
nextTick
(()
=>
{
expect
(
this
.
vm
.
shouldHideEditor
).
toBe
(
false
);
expect
(
this
.
vm
.
$el
.
id
).
toEqual
(
'
ide
'
);
expect
(
this
.
vm
.
$el
.
tagName
).
toBe
(
'
DIV
'
);
done
();
});
});
monaco
.
editor
.
create
.
and
.
returnValue
(
monacoInstance
);
spyOn
(
repoEditor
.
watch
,
'
blobRaw
'
);
describe
(
'
when there are no open files
'
,
()
=>
{
it
(
'
does not render the ide
'
,
(
done
)
=>
{
this
.
vm
.
openedFiles
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
this
.
vm
.
shouldHideEditor
).
toBe
(
true
);
expect
(
this
.
vm
.
$el
.
tagName
).
not
.
toBeDefined
();
done
();
});
});
});
const
vm
=
createComponent
();
describe
(
'
when open file is binary and not raw
'
,
()
=>
{
it
(
'
does not render the IDE
'
,
(
done
)
=>
{
this
.
vm
.
binary
=
true
;
this
.
vm
.
activeFile
=
{
raw
:
false
,
};
expect
(
vm
.
$el
.
id
).
toEqual
(
'
ide
'
);
Vue
.
nextTick
(()
=>
{
expect
(
this
.
vm
.
shouldHideEditor
).
toBe
(
true
);
expect
(
this
.
vm
.
$el
.
tagName
).
not
.
toBeDefined
();
done
();
});
});
});
});
spec/javascripts/repo/components/repo_file_buttons_spec.js
View file @
695caab0
...
...
@@ -23,6 +23,7 @@ describe('RepoFileButtons', () => {
RepoStore
.
activeFile
=
activeFile
;
RepoStore
.
activeFileLabel
=
activeFileLabel
;
RepoStore
.
editMode
=
true
;
RepoStore
.
binary
=
false
;
const
vm
=
createComponent
();
const
raw
=
vm
.
$el
.
querySelector
(
'
.raw
'
);
...
...
spec/javascripts/repo/components/repo_tab_spec.js
View file @
695caab0
...
...
@@ -21,7 +21,7 @@ describe('RepoTab', () => {
const
close
=
vm
.
$el
.
querySelector
(
'
.close
'
);
const
name
=
vm
.
$el
.
querySelector
(
`a[title="
${
tab
.
url
}
"]`
);
spyOn
(
vm
,
'
xClicked
'
);
spyOn
(
vm
,
'
closeTab
'
);
spyOn
(
vm
,
'
tabClicked
'
);
expect
(
close
.
querySelector
(
'
.fa-times
'
)).
toBeTruthy
();
...
...
@@ -30,7 +30,7 @@ describe('RepoTab', () => {
close
.
click
();
name
.
click
();
expect
(
vm
.
xClicked
).
toHaveBeenCalledWith
(
tab
);
expect
(
vm
.
closeTab
).
toHaveBeenCalledWith
(
tab
);
expect
(
vm
.
tabClicked
).
toHaveBeenCalledWith
(
tab
);
});
...
...
@@ -48,22 +48,22 @@ describe('RepoTab', () => {
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
xClicked
'
,
()
=>
{
describe
(
'
closeTab
'
,
()
=>
{
const
vm
=
jasmine
.
createSpyObj
(
'
vm
'
,
[
'
$emit
'
]);
it
(
'
returns undefined and does not $emit if file is changed
'
,
()
=>
{
const
file
=
{
changed
:
true
};
const
returnVal
=
repoTab
.
methods
.
xClicked
.
call
(
vm
,
file
);
const
returnVal
=
repoTab
.
methods
.
closeTab
.
call
(
vm
,
file
);
expect
(
returnVal
).
toBeUndefined
();
expect
(
vm
.
$emit
).
not
.
toHaveBeenCalled
();
});
it
(
'
$emits
xclick
ed event with file obj
'
,
()
=>
{
it
(
'
$emits
tabclos
ed event with file obj
'
,
()
=>
{
const
file
=
{
changed
:
false
};
repoTab
.
methods
.
xClicked
.
call
(
vm
,
file
);
repoTab
.
methods
.
closeTab
.
call
(
vm
,
file
);
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
xclick
ed
'
,
file
);
expect
(
vm
.
$emit
).
toHaveBeenCalledWith
(
'
tabclos
ed
'
,
file
);
});
});
});
...
...
spec/javascripts/repo/components/repo_tabs_spec.js
View file @
695caab0
...
...
@@ -38,13 +38,13 @@ describe('RepoTabs', () => {
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
xClick
ed
'
,
()
=>
{
describe
(
'
tabClos
ed
'
,
()
=>
{
it
(
'
calls removeFromOpenedFiles with file obj
'
,
()
=>
{
const
file
=
{};
spyOn
(
RepoStore
,
'
removeFromOpenedFiles
'
);
repoTabs
.
methods
.
xClick
ed
(
file
);
repoTabs
.
methods
.
tabClos
ed
(
file
);
expect
(
RepoStore
.
removeFromOpenedFiles
).
toHaveBeenCalledWith
(
file
);
});
...
...
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