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
13e73ec4
Unverified
Commit
13e73ec4
authored
Jun 28, 2019
by
Kyle Keating
Committed by
GitHub
Jun 28, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1198 from hypothesis/react-moderation-banner
Add react moderation banner component
parents
cbb01a67
f8318a3b
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
165 additions
and
113 deletions
+165
-113
moderation-banner.js
src/sidebar/components/moderation-banner.js
+78
-39
annotation-thread-test.js
src/sidebar/components/test/annotation-thread-test.js
+2
-4
moderation-banner-test.js
src/sidebar/components/test/moderation-banner-test.js
+78
-67
index.js
src/sidebar/index.js
+4
-1
annotation-thread.html
src/sidebar/templates/annotation-thread.html
+3
-2
No files found.
src/sidebar/components/moderation-banner.js
View file @
13e73ec4
'use strict'
;
const
annotationMetadata
=
require
(
'../annotation-metadata'
);
// @ngInject
function
ModerationBannerController
(
store
,
flash
,
api
)
{
const
self
=
this
;
const
{
createElement
}
=
require
(
'preact'
);
const
classnames
=
require
(
'classnames'
);
const
propTypes
=
require
(
'prop-types'
);
this
.
flagCount
=
function
()
{
return
annotationMetadata
.
flagCount
(
self
.
annotation
);
}
;
const
annotationMetadata
=
require
(
'../annotation-metadata'
);
const
useStore
=
require
(
'../store/use-store'
);
const
{
withServices
}
=
require
(
'../util/service-context'
)
;
this
.
isHidden
=
function
()
{
return
self
.
annotation
.
hidden
;
};
/**
* Banner allows moderators to hide/unhide the flagged
* annotation from other users.
*/
function
ModerationBanner
({
annotation
,
api
,
flash
})
{
// actions
const
store
=
useStore
(
store
=>
({
hide
:
store
.
hideAnnotation
,
unhide
:
store
.
unhideAnnotation
,
}));
this
.
isHiddenOrFlagged
=
function
()
{
const
flagCount
=
self
.
flagCount
();
return
flagCount
!==
null
&&
(
flagCount
>
0
||
self
.
isHidden
());
};
const
flagCount
=
annotationMetadata
.
flagCount
(
annotation
);
this
.
isReply
=
function
()
{
return
annotationMetadata
.
isReply
(
self
.
annotation
);
};
const
isHiddenOrFlagged
=
flagCount
!==
null
&&
(
flagCount
>
0
||
annotation
.
hidden
);
/**
* Hide an annotation from non-moderator users.
*/
this
.
hideAnnotation
=
function
()
{
const
hideAnnotation
=
()
=>
{
api
.
annotation
.
hide
({
id
:
self
.
annotation
.
id
})
.
then
(
function
()
{
store
.
hide
Annotation
(
self
.
annotation
.
id
);
.
hide
({
id
:
annotation
.
id
})
.
then
(
()
=>
{
store
.
hide
(
annotation
.
id
);
})
.
catch
(
function
()
{
.
catch
(
()
=>
{
flash
.
error
(
'Failed to hide annotation'
);
});
};
...
...
@@ -40,28 +41,66 @@ function ModerationBannerController(store, flash, api) {
/**
* Un-hide an annotation from non-moderator users.
*/
this
.
unhideAnnotation
=
function
()
{
const
unhideAnnotation
=
()
=>
{
api
.
annotation
.
unhide
({
id
:
self
.
annotation
.
id
})
.
then
(
function
()
{
store
.
unhide
Annotation
(
self
.
annotation
.
id
);
.
unhide
({
id
:
annotation
.
id
})
.
then
(
()
=>
{
store
.
unhide
(
annotation
.
id
);
})
.
catch
(
function
()
{
.
catch
(
()
=>
{
flash
.
error
(
'Failed to unhide annotation'
);
});
};
const
toggleButtonProps
=
(()
=>
{
const
props
=
{};
if
(
annotation
.
hidden
)
{
props
.
onClick
=
unhideAnnotation
;
props
.
title
=
'Make this annotation visible to everyone'
;
}
else
{
props
.
onClick
=
hideAnnotation
;
props
.
title
=
'Hide this annotation from non-moderators'
;
}
return
props
;
})();
const
bannerClasses
=
classnames
(
'moderation-banner'
,
{
'is-flagged'
:
flagCount
>
0
,
'is-hidden'
:
annotation
.
hidden
,
'is-reply'
:
annotationMetadata
.
isReply
(
annotation
),
});
if
(
!
isHiddenOrFlagged
)
{
return
null
;
}
return
(
<
div
className
=
{
bannerClasses
}
>
{
flagCount
&&
!
annotation
.
hidden
&&
(
<
span
>
Flagged
for
review
x
{
flagCount
}
<
/span
>
)}
{
annotation
.
hidden
&&
(
<
span
>
Hidden
from
users
.
Flagged
x
{
flagCount
}
<
/span
>
)}
<
span
className
=
"u-stretch"
><
/span
>
<
button
{...
toggleButtonProps
}
>
{
annotation
.
hidden
?
'Unhide'
:
'Hide'
}
<
/button
>
<
/div
>
);
}
/**
* Banner shown above flagged annotations to allow moderators to hide/unhide the
* annotation from other users.
ModerationBanner
.
propTypes
=
{
/**
* The annotation object for this banner. This contains
* state about the flag count or its hidden value.
*/
annotation
:
propTypes
.
object
.
isRequired
,
module
.
exports
=
{
controller
:
ModerationBannerController
,
controllerAs
:
'vm'
,
bindings
:
{
annotation
:
'<'
,
},
template
:
require
(
'../templates/moderation-banner.html'
),
// Injected services.
api
:
propTypes
.
object
.
isRequired
,
flash
:
propTypes
.
object
.
isRequired
,
};
ModerationBanner
.
injectedProps
=
[
'api'
,
'flash'
];
module
.
exports
=
withServices
(
ModerationBanner
);
src/sidebar/components/test/annotation-thread-test.js
View file @
13e73ec4
...
...
@@ -238,10 +238,8 @@ describe('annotationThread', function() {
const
element
=
util
.
createDirective
(
document
,
'annotationThread'
,
{
thread
:
thread
,
});
const
moderationBanner
=
element
.
find
(
'moderation-banner'
)
.
controller
(
'moderationBanner'
);
assert
.
deepEqual
(
moderationBanner
,
{
annotation
:
ann
});
assert
.
ok
(
element
[
0
].
querySelector
(
'moderation-banner'
));
assert
.
ok
(
element
[
0
].
querySelector
(
'annotation'
));
});
it
(
'does not render the annotation or moderation banner if there is no annotation'
,
function
()
{
...
...
src/sidebar/components/test/moderation-banner-test.js
View file @
13e73ec4
'use strict'
;
const
angular
=
require
(
'angular'
);
const
{
shallow
}
=
require
(
'enzyme'
);
const
{
createElement
}
=
require
(
'preact'
);
const
util
=
require
(
'../../directive/test/util
'
);
const
ModerationBanner
=
require
(
'../moderation-banner
'
);
const
fixtures
=
require
(
'../../test/annotation-fixtures'
);
const
unroll
=
require
(
'../../../shared/test/util'
).
unroll
;
const
moderatedAnnotation
=
fixtures
.
moderatedAnnotation
;
describe
(
'moderationBanner'
,
function
()
{
let
bannerEl
;
let
fakeStore
;
let
fakeFlash
;
describe
(
'ModerationBanner'
,
()
=>
{
let
fakeApi
;
let
fakeFlash
;
before
(
function
()
{
angular
.
module
(
'app'
,
[])
.
component
(
'moderationBanner'
,
require
(
'../moderation-banner'
));
});
beforeEach
(
function
()
{
fakeStore
=
{
hideAnnotation
:
sinon
.
stub
(),
unhideAnnotation
:
sinon
.
stub
(),
};
function
createComponent
(
props
)
{
return
shallow
(
<
ModerationBanner
api
=
{
fakeApi
}
flash
=
{
fakeFlash
}
{...
props
}
/
>
).
dive
();
// dive() needed because this component uses `withServices`
}
beforeEach
(()
=>
{
fakeFlash
=
{
error
:
sinon
.
stub
(),
};
...
...
@@ -37,31 +31,29 @@ describe('moderationBanner', function() {
},
};
angular
.
mock
.
module
(
'app'
,
{
store
:
fakeStore
,
api
:
fakeApi
,
flash
:
fakeFlash
,
ModerationBanner
.
$imports
.
$mock
({
'../store/use-store'
:
callback
=>
callback
({
hide
:
sinon
.
stub
(),
unhide
:
sinon
.
stub
(),
}),
});
});
afterEach
(
function
()
{
bannerEl
.
remov
e
();
afterEach
(
()
=>
{
ModerationBanner
.
$imports
.
$restor
e
();
});
function
createBanner
(
inputs
)
{
const
el
=
util
.
createDirective
(
document
,
'moderationBanner'
,
inputs
);
bannerEl
=
el
[
0
];
return
bannerEl
;
}
unroll
(
'displays if user is a moderator and annotation is hidden or flagged'
,
function
(
testCase
)
{
const
banner
=
createBanner
({
annotation
:
testCase
.
ann
});
const
wrapper
=
createComponent
({
annotation
:
testCase
.
ann
,
});
if
(
testCase
.
expectVisible
)
{
assert
.
notEqual
(
banner
.
textContent
.
trim
(),
''
);
assert
.
notEqual
(
wrapper
.
text
()
.
trim
(),
''
);
}
else
{
assert
.
equal
(
banner
.
textContent
.
trim
(),
''
);
assert
.
isFalse
(
wrapper
.
exists
()
);
}
},
[
...
...
@@ -72,9 +64,10 @@ describe('moderationBanner', function() {
},
{
// Hidden, but user is not a moderator
ann
:
Object
.
assign
(
fixtures
.
defaultAnnotation
(),
{
ann
:
{
...
fixtures
.
defaultAnnotation
(),
hidden
:
true
,
}
)
,
},
expectVisible
:
false
,
},
{
...
...
@@ -98,42 +91,62 @@ describe('moderationBanner', function() {
it
(
'displays the number of flags the annotation has received'
,
function
()
{
const
ann
=
fixtures
.
moderatedAnnotation
({
flagCount
:
10
});
const
banner
=
createBanner
({
annotation
:
ann
});
assert
.
include
(
banner
.
textContent
,
'Flagged for review x10'
);
const
wrapper
=
createComponent
({
annotation
:
ann
});
assert
.
include
(
wrapper
.
text
()
,
'Flagged for review x10'
);
});
it
(
'displays in a more compact form if the annotation is a reply'
,
function
()
{
const
ann
=
Object
.
assign
(
fixtures
.
oldReply
(),
{
const
wrapper
=
createComponent
({
annotation
:
{
...
fixtures
.
oldReply
(),
moderation
:
{
flagCount
:
10
,
},
},
});
wrapper
.
exists
(
'.is-reply'
);
});
it
(
'does not display in a more compact form if the annotation is not a reply'
,
function
()
{
const
wrapper
=
createComponent
({
annotation
:
{
...
fixtures
.
moderatedAnnotation
({}),
moderation
:
{
flagCount
:
10
,
},
},
});
const
banner
=
createBanner
({
annotation
:
ann
});
assert
.
ok
(
banner
.
querySelector
(
'.is-reply'
));
assert
.
isFalse
(
wrapper
.
exists
(
'.is-reply'
));
});
it
(
'reports if the annotation was hidden'
,
function
()
{
const
ann
=
moderatedAnnotation
({
const
wrapper
=
createComponent
({
annotation
:
fixtures
.
moderatedAnnotation
({
flagCount
:
1
,
hidden
:
true
,
}),
});
const
banner
=
createBanner
({
annotation
:
ann
});
assert
.
include
(
banner
.
textContent
,
'Hidden from users'
);
assert
.
include
(
wrapper
.
text
(),
'Hidden from users'
);
});
it
(
'hides the annotation if "Hide" is clicked'
,
function
()
{
const
ann
=
moderatedAnnotation
({
flagCount
:
10
});
const
banner
=
createBanner
({
annotation
:
ann
});
banner
.
querySelector
(
'button'
).
click
();
const
wrapper
=
createComponent
({
annotation
:
fixtures
.
moderatedAnnotation
({
flagCount
:
10
,
}),
});
wrapper
.
find
(
'button'
).
simulate
(
'click'
);
assert
.
calledWith
(
fakeApi
.
annotation
.
hide
,
{
id
:
'ann-id'
});
});
it
(
'reports an error if hiding the annotation fails'
,
function
(
done
)
{
const
ann
=
moderatedAnnotation
({
flagCount
:
10
});
const
banner
=
createBanner
({
annotation
:
ann
});
const
wrapper
=
createComponent
({
annotation
:
moderatedAnnotation
({
flagCount
:
10
,
}),
});
fakeApi
.
annotation
.
hide
.
returns
(
Promise
.
reject
(
new
Error
(
'Network Error'
)));
banner
.
querySelector
(
'button'
).
click
();
wrapper
.
find
(
'button'
).
simulate
(
'click'
);
setTimeout
(
function
()
{
assert
.
calledWith
(
fakeFlash
.
error
,
'Failed to hide annotation'
);
...
...
@@ -142,29 +155,27 @@ describe('moderationBanner', function() {
});
it
(
'unhides the annotation if "Unhide" is clicked'
,
function
()
{
const
ann
=
moderatedAnnotation
({
const
wrapper
=
createComponent
({
annotation
:
moderatedAnnotation
({
flagCount
:
1
,
hidden
:
true
,
}),
});
const
banner
=
createBanner
({
annotation
:
ann
});
banner
.
querySelector
(
'button'
).
click
();
wrapper
.
find
(
'button'
).
simulate
(
'click'
);
assert
.
calledWith
(
fakeApi
.
annotation
.
unhide
,
{
id
:
'ann-id'
});
});
it
(
'reports an error if unhiding the annotation fails'
,
function
(
done
)
{
const
ann
=
moderatedAnnotation
({
const
wrapper
=
createComponent
({
annotation
:
moderatedAnnotation
({
flagCount
:
1
,
hidden
:
true
,
}),
});
const
banner
=
createBanner
({
annotation
:
ann
});
fakeApi
.
annotation
.
unhide
.
returns
(
Promise
.
reject
(
new
Error
(
'Network Error'
))
);
banner
.
querySelector
(
'button'
).
click
();
wrapper
.
find
(
'button'
).
simulate
(
'click'
);
setTimeout
(
function
()
{
assert
.
calledWith
(
fakeFlash
.
error
,
'Failed to unhide annotation'
);
done
();
...
...
src/sidebar/index.js
View file @
13e73ec4
...
...
@@ -176,7 +176,10 @@ function startAngularApp(config) {
.
component
(
'loggedoutMessage'
,
require
(
'./components/loggedout-message'
))
.
component
(
'loginControl'
,
require
(
'./components/login-control'
))
.
component
(
'markdown'
,
require
(
'./components/markdown'
))
.
component
(
'moderationBanner'
,
require
(
'./components/moderation-banner'
))
.
component
(
'moderationBanner'
,
wrapReactComponent
(
require
(
'./components/moderation-banner'
))
)
.
component
(
'newNoteBtn'
,
require
(
'./components/new-note-btn'
))
.
component
(
'searchInput'
,
...
...
src/sidebar/templates/annotation-thread.html
View file @
13e73ec4
...
...
@@ -10,7 +10,8 @@
<div
class=
"annotation-thread__thread-line"
></div>
</div>
<div
class=
"annotation-thread__content"
>
<moderation-banner
annotation=
"vm.thread.annotation"
<moderation-banner
annotation=
"vm.thread.annotation"
ng-if=
"vm.thread.annotation"
>
</moderation-banner>
<annotation
ng-class=
"vm.annotationClasses()"
...
...
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