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
cdd54488
Commit
cdd54488
authored
Jul 09, 2019
by
Hannah Stepanek
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Convert search-status-bar to preact
parent
b5d16912
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
268 additions
and
222 deletions
+268
-222
search-status-bar.js
src/sidebar/components/search-status-bar.js
+101
-43
search-status-bar-test.js
src/sidebar/components/test/search-status-bar-test.js
+161
-142
index.js
src/sidebar/index.js
+4
-1
search-status-bar.html
src/sidebar/templates/search-status-bar.html
+0
-35
sidebar-content.html
src/sidebar/templates/sidebar-content.html
+2
-1
No files found.
src/sidebar/components/search-status-bar.js
View file @
cdd54488
'use strict'
;
'use strict'
;
const
memoize
=
require
(
'../util/memoize'
);
const
{
Fragment
,
createElement
}
=
require
(
'preact'
);
const
propTypes
=
require
(
'prop-types'
);
const
{
useMemo
}
=
require
(
'preact/hooks'
);
const
{
withServices
}
=
require
(
'../util/service-context'
);
const
uiConstants
=
require
(
'../ui-constants'
);
const
uiConstants
=
require
(
'../ui-constants'
);
const
useStore
=
require
(
'../store/use-store'
);
// @ngInject
const
countVisibleAnns
=
annThread
=>
{
function
SearchStatusBarController
(
store
,
rootThread
)
{
return
annThread
.
children
.
reduce
(
this
.
TAB_ANNOTATIONS
=
uiConstants
.
TAB_ANNOTATIONS
;
function
(
count
,
child
)
{
this
.
TAB_NOTES
=
uiConstants
.
TAB_NOTES
;
return
count
+
countVisibleAnns
(
child
);
this
.
TAB_ORPHANS
=
uiConstants
.
TAB_ORPHANS
;
},
annThread
.
visible
?
1
:
0
);
};
const
thread
=
()
=>
{
/**
return
rootThread
.
thread
(
store
.
getState
());
* A bar where the user can clear a selection or search and see whether
};
* any search results were found.
* */
function
SearchStatusBar
({
selectedTab
,
totalAnnotations
,
totalNotes
,
rootThread
,
})
{
const
storeState
=
useStore
(
store
=>
store
.
getState
());
const
clearSelection
=
useStore
(
store
=>
store
.
clearSelection
);
const
filterQuery
=
storeState
.
filterQuery
;
const
filterActive
=
!!
storeState
.
filterQuery
;
const
thread
=
rootThread
.
thread
(
storeState
);
const
visibleCount
=
memoize
(
thread
=>
{
const
visibleCount
=
useMemo
(()
=>
{
return
thread
.
children
.
reduce
(
return
countVisibleAnns
(
thread
);
function
(
count
,
child
)
{
},
[
thread
]);
return
count
+
visibleCount
(
child
);
},
const
filterResults
=
()
=>
{
thread
.
visible
?
1
:
0
const
resultsCount
=
visibleCount
;
);
switch
(
resultsCount
)
{
});
case
0
:
return
'No results for "'
+
filterQuery
+
'"'
;
this
.
filterMatchCount
=
function
()
{
case
1
:
return
visibleCount
(
thread
());
return
'1 search result'
;
default
:
return
resultsCount
+
' search results'
;
}
};
};
this
.
areAllAnnotationsVisible
=
function
()
{
const
areNotAllAnnotationsVisible
=
()
=>
{
if
(
store
.
getState
()
.
directLinkedGroupFetchFailed
)
{
if
(
store
State
.
directLinkedGroupFetchFailed
)
{
return
true
;
return
true
;
}
}
const
selection
=
store
.
getState
()
.
selectedAnnotationMap
;
const
selection
=
store
State
.
selectedAnnotationMap
;
if
(
!
selection
)
{
if
(
!
selection
)
{
return
false
;
return
false
;
}
}
return
Object
.
keys
(
selection
).
length
>
0
;
return
Object
.
keys
(
selection
).
length
>
0
;
};
};
this
.
filterQuery
=
function
()
{
return
(
return
store
.
getState
().
filterQuery
;
<
div
>
};
{
filterActive
&&
(
<
div
className
=
"search-status-bar"
>
this
.
filterActive
=
function
()
{
<
button
return
!!
store
.
getState
().
filterQuery
;
className
=
"primary-action-btn primary-action-btn--short"
};
onClick
=
{
clearSelection
}
title
=
"Clear the search filter and show all annotations"
this
.
onClearSelection
=
function
()
{
>
store
.
clearSelection
();
<
i
className
=
"primary-action-btn__icon h-icon-close"
/>
};
Clear
search
<
/button
>
<
span
>
{
filterResults
()}
<
/span
>
<
/div
>
)}
{
!
filterActive
&&
areNotAllAnnotationsVisible
()
&&
(
<
div
className
=
"search-status-bar"
>
<
button
className
=
"primary-action-btn primary-action-btn--short"
onClick
=
{
clearSelection
}
title
=
"Clear the selection and show all annotations"
>
{
selectedTab
===
uiConstants
.
TAB_ORPHANS
&&
(
<
Fragment
>
Show
all
annotations
and
notes
<
/Fragment
>
)}
{
selectedTab
===
uiConstants
.
TAB_ANNOTATIONS
&&
(
<
Fragment
>
Show
all
annotations
{
totalAnnotations
>
1
&&
<
span
>
({
totalAnnotations
})
<
/span>
}
<
/Fragment
>
)}
{
selectedTab
===
uiConstants
.
TAB_NOTES
&&
(
<
Fragment
>
Show
all
notes
{
totalNotes
>
1
&&
<
span
>
({
totalNotes
})
<
/span>
}
<
/Fragment
>
)}
<
/button
>
<
/div
>
)}
<
/div
>
);
}
}
module
.
export
s
=
{
SearchStatusBar
.
propType
s
=
{
controller
:
SearchStatusBarController
,
selectedTab
:
propTypes
.
oneOf
([
controllerAs
:
'vm'
,
uiConstants
.
TAB_ANNOTATIONS
,
bindings
:
{
uiConstants
.
TAB_ORPHANS
,
selectedTab
:
'<'
,
uiConstants
.
TAB_NOTES
,
totalAnnotations
:
'<'
,
]).
isRequired
,
totalNotes
:
'<'
,
totalAnnotations
:
propTypes
.
number
.
isRequired
,
}
,
totalNotes
:
propTypes
.
number
.
isRequired
,
template
:
require
(
'../templates/search-status-bar.html'
)
,
rootThread
:
propTypes
.
object
.
isRequired
,
};
};
SearchStatusBar
.
injectedProps
=
[
'rootThread'
];
module
.
exports
=
withServices
(
SearchStatusBar
);
src/sidebar/components/test/search-status-bar-test.js
View file @
cdd54488
'use strict'
;
'use strict'
;
const
angular
=
require
(
'angular'
);
const
{
shallow
}
=
require
(
'enzyme'
);
const
{
createElement
}
=
require
(
'preact'
);
const
util
=
require
(
'../../directive/test/util'
);
const
SearchStatusBar
=
require
(
'../search-status-bar'
);
describe
(
'searchStatusBar'
,
()
=>
{
before
(()
=>
{
angular
.
module
(
'app'
,
[])
.
component
(
'searchStatusBar'
,
require
(
'../search-status-bar'
));
});
describe
(
'SearchStatusBar'
,
()
=>
{
let
fakeRootThread
;
let
fakeRootThread
;
let
fakeStore
;
let
fakeStore
;
function
createComponent
(
props
)
{
return
shallow
(
<
SearchStatusBar
rootThread
=
{
fakeRootThread
}
{...
props
}
/
>
).
dive
();
// dive() needed because this component uses `withServices`
}
beforeEach
(()
=>
{
beforeEach
(()
=>
{
fakeRootThread
=
{
fakeRootThread
=
{
thread
:
sinon
.
stub
(),
thread
:
sinon
.
stub
()
.
returns
({
children
:
[]
})
,
};
};
fakeStore
=
{
fakeStore
=
{
getState
:
sinon
.
stub
(),
getState
:
sinon
.
stub
(),
...
@@ -26,174 +27,192 @@ describe('searchStatusBar', () => {
...
@@ -26,174 +27,192 @@ describe('searchStatusBar', () => {
clearDirectLinkedIds
:
sinon
.
stub
(),
clearDirectLinkedIds
:
sinon
.
stub
(),
clearSelection
:
sinon
.
stub
(),
clearSelection
:
sinon
.
stub
(),
};
};
angular
.
mock
.
module
(
'app'
,
{
store
:
fakeStore
,
rootThread
:
fakeRootThread
,
});
});
describe
(
'filterQuery'
,
()
=>
{
[
'tag:foo'
,
null
].
forEach
(
filterQuery
=>
{
it
(
'returns the `filterQuery`'
,
()
=>
{
fakeStore
.
getState
.
returns
({
filterQuery
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
const
ctrl
=
elem
.
ctrl
;
assert
.
equal
(
ctrl
.
filterQuery
(),
filterQuery
);
SearchStatusBar
.
$imports
.
$mock
({
});
'../store/use-store'
:
callback
=>
callback
(
fakeStore
),
});
});
});
});
describe
(
'filterActive'
,
()
=>
{
afterEach
(()
=>
{
it
(
'returns true if there is a `filterQuery`'
,
()
=>
{
SearchStatusBar
.
$imports
.
$restore
();
fakeStore
.
getState
.
returns
({
filterQuery
:
'tag:foo'
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
const
ctrl
=
elem
.
ctrl
;
assert
.
isTrue
(
ctrl
.
filterActive
());
});
it
(
'returns false if `filterQuery` is null'
,
()
=>
{
fakeStore
.
getState
.
returns
({
filterQuery
:
null
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
const
ctrl
=
elem
.
ctrl
;
assert
.
isFalse
(
ctrl
.
filterActive
());
});
});
});
describe
(
'filterMatchCount'
,
()
=>
{
[
it
(
'returns the total number of visible annotations or replies'
,
()
=>
{
{
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
,
()
=>
{
fakeRootThread
.
thread
.
returns
({
fakeRootThread
.
thread
.
returns
({
children
:
[
children
:
test
.
children
,
{
id
:
'1'
,
visible
:
true
,
children
:
[{
id
:
'3'
,
visible
:
true
,
children
:
[]
}],
},
{
id
:
'2'
,
visible
:
false
,
children
:
[],
},
],
});
});
fakeStore
.
getState
.
returns
({
fakeStore
.
getState
.
returns
({
filterQuery
:
'tag:foo'
,
filterQuery
:
'tag:foo'
,
});
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
const
wrapper
=
createComponent
({
const
ctrl
=
elem
.
ctrl
;
selectedTab
:
'annotation'
,
totalAnnotations
:
3
,
totalNotes
:
0
,
});
const
buttonText
=
wrapper
.
find
(
'button'
).
text
();
assert
.
equal
(
buttonText
,
'Clear search'
);
assert
.
equal
(
ctrl
.
filterMatchCount
(),
2
);
const
searchResultsText
=
wrapper
.
find
(
'span'
).
text
();
assert
.
equal
(
searchResultsText
,
test
.
expectedText
);
});
});
});
});
describe
(
'areAllAnnotationsVisible'
,
()
=>
{
it
(
'displays "Show all annotations" button when a direct-linked group fetch fails'
,
()
=>
{
it
(
'returns true if the direct-linked group fetch failed'
,
()
=>
{
fakeStore
.
getState
.
returns
({
fakeStore
.
getState
.
returns
({
directLinkedGroupFetchFailed
:
true
});
filterQuery
:
null
,
directLinkedGroupFetchFailed
:
true
,
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
selectedAnnotationMap
:
{
annId
:
true
},
const
ctrl
=
elem
.
ctrl
;
assert
.
isTrue
(
ctrl
.
areAllAnnotationsVisible
());
});
});
it
(
'returns true if there are annotations selected'
,
()
=>
{
const
wrapper
=
createComponent
({
fakeStore
.
getState
.
returns
({
selectedTab
:
'annotation'
,
directLinkedGroupFetchFailed
:
false
,
totalAnnotations
:
1
,
selectedAnnotationMap
:
{
ann
:
true
},
totalNotes
:
0
,
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
const
ctrl
=
elem
.
ctrl
;
assert
.
isTrue
(
ctrl
.
areAllAnnotationsVisible
());
});
});
it
(
'returns false if there are no annotations selected'
,
()
=>
{
const
buttonText
=
wrapper
.
find
(
'button'
).
text
();
fakeStore
.
getState
.
returns
({
assert
.
equal
(
buttonText
,
'Show all annotations'
);
directLinkedGroupFetchFailed
:
false
,
});
selectedAnnotationMap
:
{},
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
const
ctrl
=
elem
.
ctrl
;
assert
.
isFalse
(
ctrl
.
areAllAnnotationsVisible
());
it
(
'displays "Show all annotations" button when there are selected annotations'
,
()
=>
{
fakeStore
.
getState
.
returns
({
filterQuery
:
null
,
directLinkedGroupFetchFailed
:
false
,
selectedAnnotationMap
:
{
annId
:
true
},
});
});
it
(
'returns false if the `selectedAnnotationMap` is null'
,
()
=>
{
const
wrapper
=
createComponent
({
fakeStore
.
getState
.
returns
({
selectedTab
:
'annotation'
,
directLinkedGroupFetchFailed
:
false
,
totalAnnotations
:
1
,
selectedAnnotationMap
:
null
,
totalNotes
:
0
,
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
const
ctrl
=
elem
.
ctrl
;
assert
.
isFalse
(
ctrl
.
areAllAnnotationsVisible
());
});
});
});
context
(
'when there is a filter'
,
()
=>
{
const
buttonText
=
wrapper
.
find
(
'button'
).
text
();
it
(
'should display the filter count'
,
()
=>
{
assert
.
equal
(
buttonText
,
'Show all annotations'
);
fakeRootThread
.
thread
.
returns
({
children
:
[
{
id
:
'1'
,
visible
:
true
,
children
:
[{
id
:
'3'
,
visible
:
true
,
children
:
[]
}],
},
{
id
:
'2'
,
visible
:
false
,
children
:
[],
},
],
});
fakeStore
.
getState
.
returns
({
filterQuery
:
'tag:foo'
,
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{});
assert
.
include
(
elem
[
0
].
textContent
,
'2 search results'
);
});
});
});
context
(
'when there is a selection'
,
()
=>
{
[
null
,
{}].
forEach
(
selectedAnnotationMap
=>
{
it
(
'should display the "Show all annotations (2)" message when there are 2 annotations'
,
()
=>
{
it
(
'does not display "Show all annotations" button when there are no selected annotations'
,
()
=>
{
const
msg
=
'Show all annotations'
;
const
msgCount
=
'(2)'
;
fakeStore
.
getState
.
returns
({
fakeStore
.
getState
.
returns
({
selectedAnnotationMap
:
{
ann1
:
true
},
filterQuery
:
null
,
directLinkedGroupFetchFailed
:
false
,
selectedAnnotationMap
:
selectedAnnotationMap
,
});
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{
const
wrapper
=
createComponent
({
totalAnnotations
:
2
,
selectedTab
:
'annotation'
,
selectedTab
:
'annotation'
,
totalAnnotations
:
1
,
totalNotes
:
0
,
});
});
const
clearBtn
=
elem
[
0
].
querySelector
(
'button'
);
assert
.
include
(
clearBtn
.
textContent
,
msg
);
const
buttons
=
wrapper
.
find
(
'button'
);
assert
.
include
(
clearBtn
.
textContent
,
msgCount
);
assert
.
equal
(
buttons
.
length
,
0
);
});
});
});
it
(
'should display the "Show all notes (3)" message when there are 3 notes'
,
()
=>
{
[
const
msg
=
'Show all notes'
;
{
const
msgCount
=
'(3)'
;
description
:
'displays "Show all annotations and notes" button when the orphans tab is selected'
,
selectedTab
:
'orphan'
,
totalAnnotations
:
1
,
totalNotes
:
1
,
expectedText
:
'Show all annotations and notes'
,
},
{
description
:
'displays "Show all notes" button when the notes tab is selected'
,
selectedTab
:
'note'
,
totalAnnotations
:
1
,
totalNotes
:
1
,
expectedText
:
'Show all notes'
,
},
{
description
:
'displays "Show all notes (2)" button when the notes tab is selected and there are two notes'
,
selectedTab
:
'note'
,
totalAnnotations
:
2
,
totalNotes
:
2
,
expectedText
:
'Show all notes (2)'
,
},
{
description
:
'displays "Show all annotations (2)" button when the notes tab is selected and there are two annotations'
,
selectedTab
:
'annotation'
,
totalAnnotations
:
2
,
totalNotes
:
2
,
expectedText
:
'Show all annotations (2)'
,
},
].
forEach
(
test
=>
{
it
(
test
.
description
,
()
=>
{
fakeStore
.
getState
.
returns
({
fakeStore
.
getState
.
returns
({
selectedAnnotationMap
:
{
ann1
:
true
},
filterQuery
:
null
,
directLinkedGroupFetchFailed
:
false
,
selectedAnnotationMap
:
{
annId
:
true
},
});
});
const
elem
=
util
.
createDirective
(
document
,
'searchStatusBar'
,
{
totalNotes
:
3
,
const
wrapper
=
createComponent
({
selectedTab
:
'note'
,
selectedTab
:
test
.
selectedTab
,
totalAnnotations
:
test
.
totalAnnotations
,
totalNotes
:
test
.
totalNotes
,
});
});
const
clearBtn
=
elem
[
0
].
querySelector
(
'button'
);
assert
.
include
(
clearBtn
.
textContent
,
msg
);
const
buttonText
=
wrapper
.
find
(
'button'
).
text
(
);
assert
.
include
(
clearBtn
.
textContent
,
msgCoun
t
);
assert
.
equal
(
buttonText
,
test
.
expectedTex
t
);
});
});
});
});
});
});
src/sidebar/index.js
View file @
cdd54488
...
@@ -181,7 +181,10 @@ function startAngularApp(config) {
...
@@ -181,7 +181,10 @@ function startAngularApp(config) {
wrapReactComponent
(
require
(
'./components/moderation-banner'
))
wrapReactComponent
(
require
(
'./components/moderation-banner'
))
)
)
.
component
(
'newNoteBtn'
,
require
(
'./components/new-note-btn'
))
.
component
(
'newNoteBtn'
,
require
(
'./components/new-note-btn'
))
.
component
(
'searchStatusBar'
,
require
(
'./components/search-status-bar'
))
.
component
(
'searchStatusBar'
,
wrapReactComponent
(
require
(
'./components/search-status-bar'
))
)
.
component
(
'selectionTabs'
,
require
(
'./components/selection-tabs'
))
.
component
(
'selectionTabs'
,
require
(
'./components/selection-tabs'
))
.
component
(
'sidebarContent'
,
require
(
'./components/sidebar-content'
))
.
component
(
'sidebarContent'
,
require
(
'./components/sidebar-content'
))
.
component
(
.
component
(
...
...
src/sidebar/templates/search-status-bar.html
deleted
100644 → 0
View file @
b5d16912
<div
class=
"search-status-bar"
ng-if=
"vm.filterActive()"
>
<button
class=
"primary-action-btn primary-action-btn--short"
ng-click=
"vm.onClearSelection()"
title=
"Clear the search filter and show all annotations"
>
<i
class=
"primary-action-btn__icon h-icon-close"
></i>
Clear search
</button>
<span
ng-pluralize
count=
"vm.filterMatchCount()"
when=
"{'0': 'No results for “{{vm.filterQuery()}}”',
'one': '1 search result',
'other': '{} search results'}"
></span>
</div>
<div
class=
"search-status-bar"
ng-if=
"!vm.filterActive() && vm.areAllAnnotationsVisible()"
>
<button
class=
"primary-action-btn primary-action-btn--short"
ng-click=
"vm.onClearSelection()"
title=
"Clear the selection and show all annotations"
>
<span
ng-if=
"!vm.selectedTab || vm.selectedTab === vm.TAB_ORPHANS"
>
Show all annotations and notes
</span>
<span
ng-if=
"vm.selectedTab === vm.TAB_ANNOTATIONS"
>
Show all annotations
<span
ng-if=
"vm.totalAnnotations > 1"
>
({{vm.totalAnnotations}})
</span>
</span>
<span
ng-if=
"vm.selectedTab === vm.TAB_NOTES"
>
Show all notes
<span
ng-if=
"vm.totalNotes > 1"
>
({{vm.totalNotes}})
</span>
</span>
</button>
</div>
src/sidebar/templates/sidebar-content.html
View file @
cdd54488
...
@@ -9,7 +9,8 @@
...
@@ -9,7 +9,8 @@
</selection-tabs>
</selection-tabs>
<search-status-bar
<search-status-bar
ng-show=
"!vm.isLoading()"
class=
"search-status-bar"
ng-if=
"!vm.isLoading()"
selected-tab=
"vm.selectedTab"
selected-tab=
"vm.selectedTab"
total-annotations=
"vm.totalAnnotations"
total-annotations=
"vm.totalAnnotations"
total-notes=
"vm.totalNotes"
>
total-notes=
"vm.totalNotes"
>
...
...
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