Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
51ef55a1
Commit
51ef55a1
authored
Aug 17, 2020
by
Lyza Danger Gardner
Committed by
Lyza Gardner
Aug 17, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove `SearchStatusBar` and `FocusedModeHeader`
These are replaced/consolidated by `FilterStatus`
parent
7172a059
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
0 additions
and
703 deletions
+0
-703
focused-mode-header.js
src/sidebar/components/focused-mode-header.js
+0
-58
search-status-bar.js
src/sidebar/components/search-status-bar.js
+0
-175
focused-mode-header-test.js
src/sidebar/components/test/focused-mode-header-test.js
+0
-105
search-status-bar-test.js
src/sidebar/components/test/search-status-bar-test.js
+0
-314
focused-mode-header.scss
src/styles/sidebar/components/focused-mode-header.scss
+0
-36
search-status-bar.scss
src/styles/sidebar/components/search-status-bar.scss
+0
-13
sidebar.scss
src/styles/sidebar/sidebar.scss
+0
-2
No files found.
src/sidebar/components/focused-mode-header.js
deleted
100644 → 0
View file @
7172a059
import
{
createElement
}
from
'preact'
;
import
useStore
from
'../store/use-store'
;
/**
* Render a control to interact with any focused "mode" in the sidebar.
* Currently only a user-focus mode is supported but this could be broadened
* and abstracted if needed. Allow user to toggle in and out of the focus "mode."
*/
export
default
function
FocusedModeHeader
()
{
const
toggleFocusMode
=
useStore
(
store
=>
store
.
toggleFocusMode
);
const
selectors
=
useStore
(
store
=>
({
focusModeActive
:
store
.
focusModeActive
(),
focusModeConfigured
:
store
.
focusModeConfigured
(),
focusModeUserPrettyName
:
store
.
focusModeUserPrettyName
(),
}));
// Nothing to do here for now if we're not focused on a user
if
(
!
selectors
.
focusModeConfigured
)
{
return
null
;
}
const
filterStatus
=
(
<
div
className
=
"focused-mode-header__filter-status"
>
{
selectors
.
focusModeActive
?
(
<
span
>
Showing
<
strong
>
{
selectors
.
focusModeUserPrettyName
}
<
/strong> onl
y
<
/span
>
)
:
(
<
span
>
Showing
<
strong
>
all
<
/strong
>
<
/span
>
)}
<
/div
>
);
const
buttonText
=
(()
=>
{
if
(
selectors
.
focusModeActive
)
{
return
'Show all'
;
}
else
{
return
`Show only
${
selectors
.
focusModeUserPrettyName
}
`
;
}
})();
return
(
<
div
className
=
"focused-mode-header"
>
{
filterStatus
}
<
button
onClick
=
{()
=>
toggleFocusMode
()}
className
=
"focused-mode-header__btn"
>
{
buttonText
}
<
/button
>
<
/div
>
);
}
FocusedModeHeader
.
propTypes
=
{};
src/sidebar/components/search-status-bar.js
deleted
100644 → 0
View file @
7172a059
import
{
createElement
}
from
'preact'
;
import
{
useMemo
}
from
'preact/hooks'
;
import
useStore
from
'../store/use-store'
;
import
uiConstants
from
'../ui-constants'
;
import
useRootThread
from
'./hooks/use-root-thread'
;
import
Button
from
'./button'
;
/**
* Of the annotations in the thread `annThread`, how many
* are currently `visible` in the browser (sidebar)?
*/
const
countVisibleAnns
=
annThread
=>
{
return
annThread
.
children
.
reduce
(
function
(
count
,
child
)
{
return
count
+
countVisibleAnns
(
child
);
},
annThread
.
visible
?
1
:
0
);
};
/**
* UI for displaying information about the currently-applied filtering of
* annotations, and, in some cases, a mechanism for clearing the filter(s).
* */
function
SearchStatusBar
()
{
const
thread
=
useRootThread
();
const
actions
=
useStore
(
store
=>
({
clearSelection
:
store
.
clearSelection
,
}));
const
counts
=
useStore
(
store
=>
({
annotations
:
store
.
annotationCount
(),
notes
:
store
.
noteCount
(),
}));
const
{
filterQuery
,
focusModeActive
,
focusModeUserPrettyName
,
hasSelectedAnnotations
,
selectedTab
,
}
=
useStore
(
store
=>
({
filterQuery
:
store
.
getState
().
selection
.
filterQuery
,
focusModeActive
:
store
.
focusModeActive
(),
focusModeUserPrettyName
:
store
.
focusModeUserPrettyName
(),
hasSelectedAnnotations
:
store
.
hasSelectedAnnotations
(),
selectedTab
:
store
.
getState
().
selection
.
selectedTab
,
}));
// The search status bar UI represents multiple "modes" of filtering
const
modes
=
{
/**
* @type {Boolean}
* A search (filter) query, visible to the user in the search bar, is
* currently applied
*/
filtered
:
!!
filterQuery
,
/**
* @type {Boolean}
* The client has a currently-applied focus on a single user. Superseded by
* `filtered` mode.
*/
focused
:
focusModeActive
&&
!
filterQuery
,
/**
* @type {Boolean}
* 0 - n annotations are currently "selected", by, e.g. clicking on highlighted
* text in the host page, direct-linking to an annotation, etc. Superseded by
* `filtered` mode.
*/
selected
:
(()
=>
{
return
hasSelectedAnnotations
&&
!
filterQuery
;
})(),
};
const
visibleCount
=
useMemo
(()
=>
{
return
countVisibleAnns
(
thread
);
},
[
thread
]);
// Each "mode" has corresponding descriptive text about the number of
// matching/applicable annotations and, sometimes, a way to clear the
// filter
const
modeText
=
{
filtered
:
(()
=>
{
switch
(
visibleCount
)
{
case
0
:
return
`No results for "
${
filterQuery
}
"`
;
case
1
:
return
'1 search result'
;
default
:
return
`
${
visibleCount
}
search results`
;
}
})(),
focused
:
(()
=>
{
switch
(
visibleCount
)
{
case
0
:
return
`No annotations for
${
focusModeUserPrettyName
}
`
;
case
1
:
return
'Showing 1 annotation'
;
default
:
return
`Showing
${
visibleCount
}
annotations`
;
}
})(),
selected
:
(()
=>
{
// Generate the proper text to show on the clear-selection button.
// For non-user-focused modes, we can display the number of annotations
// that will be visible if the selection is cleared (`counts.annotations`)
// but this number is inaccurate/misleading when also focused on a user.
let
selectedText
=
''
;
switch
(
selectedTab
)
{
case
uiConstants
.
TAB_ORPHANS
:
selectedText
=
'Show all annotations and notes'
;
break
;
case
uiConstants
.
TAB_NOTES
:
selectedText
=
'Show all notes'
;
if
(
counts
.
notes
>
1
&&
!
modes
.
focused
)
{
selectedText
+=
` (
${
counts
.
notes
}
)`
;
}
else
if
(
modes
.
focused
)
{
selectedText
+=
` by
${
focusModeUserPrettyName
}
`
;
}
break
;
case
uiConstants
.
TAB_ANNOTATIONS
:
selectedText
=
'Show all annotations'
;
if
(
counts
.
annotations
>
1
&&
!
modes
.
focused
)
{
selectedText
+=
` (
${
counts
.
annotations
}
)`
;
}
else
if
(
modes
.
focused
)
{
selectedText
=
`Show all annotations by
${
focusModeUserPrettyName
}
`
;
}
break
;
}
return
selectedText
;
})(),
};
return
(
<
div
>
{
modes
.
filtered
&&
(
<
div
className
=
"search-status-bar"
>
<
Button
icon
=
"cancel"
buttonText
=
"Clear search"
onClick
=
{
actions
.
clearSelection
}
className
=
"search-status-bar__button"
/>
<
span
className
=
"search-status-bar__filtered-text"
>
{
modeText
.
filtered
}
<
/span
>
<
/div
>
)}
{
modes
.
focused
&&
(
<
div
className
=
"search-status-bar"
>
<
span
className
=
"search-status-bar__focused-text"
>
<
strong
>
{
modeText
.
focused
}
<
/strong
>
<
/span
>
<
/div
>
)}
{
modes
.
selected
&&
(
<
div
className
=
"search-status-bar"
>
<
Button
buttonText
=
{
modeText
.
selected
}
onClick
=
{
actions
.
clearSelection
}
className
=
"search-status-bar__button"
/>
<
/div
>
)}
<
/div
>
);
}
// Necessary for test-mocking purposes (`mockImportedComponents`)
SearchStatusBar
.
propTypes
=
{};
export
default
SearchStatusBar
;
src/sidebar/components/test/focused-mode-header-test.js
deleted
100644 → 0
View file @
7172a059
import
{
mount
}
from
'enzyme'
;
import
{
createElement
}
from
'preact'
;
import
FocusedModeHeader
from
'../focused-mode-header'
;
import
{
$imports
}
from
'../focused-mode-header'
;
import
{
checkAccessibility
}
from
'../../../test-util/accessibility'
;
import
mockImportedComponents
from
'../../../test-util/mock-imported-components'
;
describe
(
'FocusedModeHeader'
,
function
()
{
let
fakeStore
;
function
createComponent
()
{
return
mount
(
<
FocusedModeHeader
/>
);
}
beforeEach
(
function
()
{
fakeStore
=
{
selection
:
{
focusMode
:
{
enabled
:
true
,
focused
:
true
,
},
},
focusModeActive
:
sinon
.
stub
().
returns
(
true
),
focusModeConfigured
:
sinon
.
stub
().
returns
(
true
),
focusModeUserPrettyName
:
sinon
.
stub
().
returns
(
'Fake User'
),
toggleFocusMode
:
sinon
.
stub
(),
};
$imports
.
$mock
(
mockImportedComponents
());
$imports
.
$mock
({
'../store/use-store'
:
callback
=>
callback
(
fakeStore
),
});
});
afterEach
(()
=>
{
$imports
.
$restore
();
});
context
(
'not in user-focused mode'
,
()
=>
{
it
(
'should not render anything if not in user-focused mode'
,
()
=>
{
fakeStore
.
focusModeConfigured
.
returns
(
false
);
const
wrapper
=
createComponent
();
assert
.
isFalse
(
wrapper
.
exists
(
'.focused-mode-header'
));
});
});
context
(
'user-focused mode'
,
()
=>
{
context
(
'focus is applied (focused/on)'
,
()
=>
{
it
(
"should render status text indicating only that user's annotations are visible"
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
match
(
wrapper
.
text
(),
/Showing.+Fake User.+only/
);
});
it
(
'should render a button allowing the user to view all annotations'
,
()
=>
{
const
wrapper
=
createComponent
();
const
button
=
wrapper
.
find
(
'button'
);
assert
.
include
(
button
.
text
(),
'Show all'
);
});
});
context
(
'focus is not applied (unfocused/off)'
,
()
=>
{
beforeEach
(()
=>
{
fakeStore
.
focusModeActive
=
sinon
.
stub
().
returns
(
false
);
});
it
(
"should render status text indicating that all user's annotations are visible"
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
match
(
wrapper
.
text
(),
/Showing.+all/
);
});
it
(
"should render a button allowing the user to view only focus user's annotations"
,
()
=>
{
const
wrapper
=
createComponent
();
const
button
=
wrapper
.
find
(
'button'
);
assert
.
include
(
button
.
text
(),
'Show only Fake User'
);
});
});
describe
(
'toggle button'
,
()
=>
{
it
(
'should toggle focus mode to false if clicked when focused'
,
()
=>
{
fakeStore
.
focusModeActive
=
sinon
.
stub
().
returns
(
true
);
const
wrapper
=
createComponent
();
wrapper
.
find
(
'button'
).
simulate
(
'click'
);
assert
.
calledOnce
(
fakeStore
.
toggleFocusMode
);
});
});
});
it
(
'should pass a11y checks'
,
checkAccessibility
({
content
:
()
=>
createComponent
(),
})
);
});
src/sidebar/components/test/search-status-bar-test.js
deleted
100644 → 0
View file @
7172a059
import
{
mount
}
from
'enzyme'
;
import
{
createElement
}
from
'preact'
;
import
SearchStatusBar
from
'../search-status-bar'
;
import
{
$imports
}
from
'../search-status-bar'
;
import
{
checkAccessibility
}
from
'../../../test-util/accessibility'
;
import
mockImportedComponents
from
'../../../test-util/mock-imported-components'
;
describe
(
'SearchStatusBar'
,
()
=>
{
let
fakeUseRootThread
;
let
fakeStore
;
function
createComponent
(
props
)
{
return
mount
(
<
SearchStatusBar
{...
props
}
/>
)
;
}
beforeEach
(()
=>
{
fakeUseRootThread
=
sinon
.
stub
().
returns
({
children
:
[]
});
fakeStore
=
{
getState
:
sinon
.
stub
().
returns
({
selection
:
{},
}),
annotationCount
:
sinon
.
stub
().
returns
(
1
),
focusModeActive
:
sinon
.
stub
().
returns
(
false
),
focusModeUserPrettyName
:
sinon
.
stub
().
returns
(
'Fake User'
),
hasSelectedAnnotations
:
sinon
.
stub
(),
noteCount
:
sinon
.
stub
().
returns
(
0
),
};
$imports
.
$mock
(
mockImportedComponents
());
$imports
.
$mock
({
'./hooks/use-root-thread'
:
fakeUseRootThread
,
'../store/use-store'
:
callback
=>
callback
(
fakeStore
),
});
});
afterEach
(()
=>
{
$imports
.
$restore
();
});
context
(
'user search query is applied'
,
()
=>
{
beforeEach
(()
=>
{
fakeStore
.
getState
.
returns
({
selection
:
{
filterQuery
:
'tag:foo'
,
selectedTab
:
'annotation'
,
},
});
fakeStore
.
annotationCount
.
returns
(
3
);
});
[
{
description
:
'shows correct text if 2 annotations match the search filter'
,
children
:
[
{
id
:
'1'
,
visible
:
true
,
children
:
[{
id
:
'3'
,
visible
:
true
,
children
:
[]
}],
},
{
id
:
'2'
,
visible
:
false
,
children
:
[],
},
],
expectedText
:
'2 search results'
,
},
{
description
:
'shows correct text if 1 annotation matches the search filter'
,
children
:
[
{
id
:
'1'
,
visible
:
true
,
children
:
[{
id
:
'3'
,
visible
:
false
,
children
:
[]
}],
},
{
id
:
'2'
,
visible
:
false
,
children
:
[],
},
],
expectedText
:
'1 search result'
,
},
{
description
:
'shows correct text if no annotation matches the search filter'
,
children
:
[
{
id
:
'1'
,
visible
:
false
,
children
:
[{
id
:
'3'
,
visible
:
false
,
children
:
[]
}],
},
{
id
:
'2'
,
visible
:
false
,
children
:
[],
},
],
expectedText
:
'No results for "tag:foo"'
,
},
].
forEach
(
test
=>
{
it
(
test
.
description
,
()
=>
{
fakeUseRootThread
.
returns
({
children
:
test
.
children
,
});
const
wrapper
=
createComponent
({});
const
button
=
wrapper
.
find
(
'Button'
);
assert
.
equal
(
button
.
props
().
buttonText
,
'Clear search'
);
const
searchResultsText
=
wrapper
.
find
(
'span'
).
text
();
assert
.
equal
(
searchResultsText
,
test
.
expectedText
);
});
});
});
context
(
'user-focused mode applied'
,
()
=>
{
beforeEach
(()
=>
{
fakeStore
.
focusModeActive
=
sinon
.
stub
().
returns
(
true
);
});
it
(
'should not display a clear/show-all-annotations button when user-focused'
,
()
=>
{
const
wrapper
=
createComponent
({});
const
buttons
=
wrapper
.
find
(
'button'
);
assert
.
equal
(
buttons
.
length
,
0
);
});
[
{
description
:
'shows pluralized annotation count when multiple annotations match for user'
,
children
:
[
{
id
:
'1'
,
visible
:
true
,
children
:
[]
},
{
id
:
'2'
,
visible
:
true
,
children
:
[]
},
],
expected
:
'Showing 2 annotations'
,
},
{
description
:
'shows single annotation count when one annotation matches for user'
,
children
:
[{
id
:
'1'
,
visible
:
true
,
children
:
[]
}],
expected
:
'Showing 1 annotation'
,
},
{
description
:
'shows "no annotations" wording when no annotations match for user'
,
children
:
[],
expected
:
'No annotations for Fake User'
,
},
].
forEach
(
test
=>
{
it
(
test
.
description
,
()
=>
{
fakeUseRootThread
.
returns
({
children
:
test
.
children
,
});
const
wrapper
=
createComponent
({});
const
resultText
=
wrapper
.
find
(
'.search-status-bar__focused-text'
)
.
text
();
assert
.
equal
(
resultText
,
test
.
expected
);
});
});
it
(
'should not display user-focused mode text if filtered mode is (also) applied'
,
()
=>
{
fakeUseRootThread
.
returns
({
children
:
[
{
id
:
'1'
,
visible
:
true
,
children
:
[]
},
{
id
:
'2'
,
visible
:
true
,
children
:
[]
},
],
});
fakeStore
.
getState
.
returns
({
selection
:
{
filterQuery
:
'tag:foo'
,
selectedTab
:
'annotation'
,
},
});
const
wrapper
=
createComponent
({});
const
focusedTextEl
=
wrapper
.
find
(
'.search-status-bar__focused-text'
);
const
filteredTextEl
=
wrapper
.
find
(
'.search-status-bar__filtered-text'
);
assert
.
isFalse
(
focusedTextEl
.
exists
());
assert
.
isTrue
(
filteredTextEl
.
exists
());
});
});
context
(
'selected-annotation(s) mode applied'
,
()
=>
{
context
(
'selected mode only'
,
()
=>
{
[
{
description
:
'should display show-all-annotation button with annotation count'
,
tab
:
'annotation'
,
annotationCount
:
5
,
noteCount
:
3
,
buttonText
:
'Show all annotations (5)'
,
},
{
description
:
'should display show-all-annotation button with annotation and notes count'
,
tab
:
'orphan'
,
annotationCount
:
5
,
noteCount
:
3
,
buttonText
:
'Show all annotations and notes'
,
},
{
description
:
'should display show-all-notes button with note count'
,
tab
:
'note'
,
annotationCount
:
3
,
noteCount
:
3
,
buttonText
:
'Show all notes (3)'
,
},
{
description
:
'should display show-all-annotation button with no count'
,
tab
:
'annotation'
,
annotationCount
:
1
,
noteCount
:
3
,
buttonText
:
'Show all annotations'
,
},
{
description
:
'should display show-all-notes button with no count'
,
tab
:
'note'
,
annotationCount
:
1
,
noteCount
:
1
,
buttonText
:
'Show all notes'
,
},
].
forEach
(
test
=>
{
it
(
test
.
description
,
()
=>
{
fakeStore
.
getState
.
returns
({
selection
:
{
filterQuery
:
null
,
selectedTab
:
test
.
tab
,
},
});
fakeStore
.
annotationCount
.
returns
(
test
.
annotationCount
);
fakeStore
.
noteCount
.
returns
(
test
.
noteCount
);
fakeStore
.
hasSelectedAnnotations
.
returns
(
true
);
const
wrapper
=
createComponent
({});
const
button
=
wrapper
.
find
(
'Button'
);
assert
.
isTrue
(
button
.
exists
());
assert
.
equal
(
button
.
props
().
buttonText
,
test
.
buttonText
);
});
});
});
context
(
'combined with applied user-focused mode'
,
()
=>
{
[
{
tab
:
'annotation'
,
buttonText
:
'Show all annotations by Fake User'
},
{
tab
:
'orphan'
,
buttonText
:
'Show all annotations and notes'
},
{
tab
:
'note'
,
buttonText
:
'Show all notes by Fake User'
},
].
forEach
(
test
=>
{
it
(
`displays correct text for tab '
${
test
.
tab
}
', without count`
,
()
=>
{
fakeStore
.
focusModeActive
=
sinon
.
stub
().
returns
(
true
);
fakeStore
.
getState
.
returns
({
selection
:
{
filterQuery
:
null
,
selectedTab
:
test
.
tab
,
},
});
fakeStore
.
annotationCount
.
returns
(
5
);
fakeStore
.
hasSelectedAnnotations
.
returns
(
true
);
fakeStore
.
noteCount
.
returns
(
3
);
const
wrapper
=
createComponent
({});
const
button
=
wrapper
.
find
(
'Button'
);
assert
.
isTrue
(
button
.
exists
());
assert
.
equal
(
button
.
props
().
buttonText
,
test
.
buttonText
);
});
});
});
context
(
'combined with applied query filter'
,
()
=>
{
// Applied-query mode wins out here; no selection UI rendered
it
(
'does not show selected-mode elements'
,
()
=>
{
fakeStore
.
focusModeActive
=
sinon
.
stub
().
returns
(
true
);
fakeStore
.
getState
.
returns
({
selection
:
{
filterQuery
:
'tag:foo'
,
selectedTab
:
'annotation'
,
},
});
fakeStore
.
annotationCount
.
returns
(
5
);
fakeStore
.
noteCount
.
returns
(
3
);
const
wrapper
=
createComponent
({});
const
button
=
wrapper
.
find
(
'Button'
);
assert
.
isTrue
(
button
.
exists
());
assert
.
equal
(
button
.
props
().
buttonText
,
'Clear search'
);
});
});
});
it
(
'should pass a11y checks'
,
checkAccessibility
({
content
:
()
=>
createComponent
({}),
})
);
});
src/styles/sidebar/components/focused-mode-header.scss
deleted
100644 → 0
View file @
7172a059
@use
"../../mixins/focus"
;
@use
"../../mixins/forms"
;
@use
"../../mixins/layout"
;
@use
"../../mixins/molecules"
;
@use
"../../mixins/utils"
;
@use
"../../variables"
as
var
;
// A dark grey button used for the primary action
// in a form
.focused-mode-header
{
@include
molecules
.
card-frame
;
@include
layout
.
row
(
$align
:
center
);
position
:
relative
;
background-color
:
var
.
$color-background
;
border-radius
:
2px
;
font-weight
:
300
;
// TODO cleanup
padding
:
var
.
$layout-space--xsmall
;
margin-bottom
:
var
.
$layout-space
;
&
__btn
{
// TODO use Button component
@include
forms
.
primary-action-btn
;
@include
layout
.
row
(
$align
:
center
);
margin-left
:
auto
;
height
:
30px
;
padding-left
:
10px
;
padding-right
:
10px
;
}
&
__filter-status
{
font-weight
:
400
;
}
}
src/styles/sidebar/components/search-status-bar.scss
deleted
100644 → 0
View file @
7172a059
@use
'../../mixins/buttons'
;
@use
'../../mixins/layout'
;
.search-status-bar
{
@include
layout
.
row
(
$align
:
center
);
margin-bottom
:
10px
;
}
.search-status-bar__button
{
@include
buttons
.
button--primary
;
padding-right
:
0
.75em
;
margin-right
:
10px
;
}
src/styles/sidebar/sidebar.scss
View file @
51ef55a1
...
...
@@ -26,7 +26,6 @@
@use
'./components/button'
;
@use
'./components/excerpt'
;
@use
'./components/filter-status'
;
@use
'./components/focused-mode-header'
;
@use
'./components/group-list'
;
@use
'./components/group-list-item'
;
@use
'./components/help-panel'
;
...
...
@@ -38,7 +37,6 @@
@use
'./components/menu-item'
;
@use
'./components/menu-section'
;
@use
'./components/moderation-banner'
;
@use
'./components/search-status-bar'
;
@use
'./components/selection-tabs'
;
@use
'./components/share-annotations-panel'
;
@use
'./components/search-input'
;
...
...
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