Commit b67fdbd7 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner

Break out `annotation-user` component from `annotation-header`

parent acde844a
'use strict';
const propTypes = require('prop-types');
const { createElement } = require('preact');
const { isThirdPartyUser, username } = require('../util/account-id');
const { withServices } = require('../util/service-context');
/**
* Display information about an annotation's user. Link to the user's
* activity if it is a first-party user or `settings.usernameUrl` is present.
*/
function AnnotationUser({ annotation, features, serviceUrl, settings }) {
const user = annotation.user;
const isFirstPartyUser = !isThirdPartyUser(user, settings.authDomain);
const username_ = username(user);
// How should the user's name be displayed?
const displayName = (() => {
if (isFirstPartyUser && !features.flagEnabled('client_display_names')) {
return username_;
}
if (annotation.user_info && annotation.user_info.display_name) {
return annotation.user_info.display_name;
}
return username_;
})();
const shouldLinkToActivity = isFirstPartyUser || settings.usernameUrl;
if (shouldLinkToActivity) {
return (
<a
className="annotation-user"
href={
isFirstPartyUser
? serviceUrl('user', { user })
: `${settings.usernameUrl}${username_}`
}
target="_blank"
rel="noopener noreferrer"
>
{displayName}
</a>
);
}
return <div className="annotation-user">{displayName}</div>;
}
AnnotationUser.propTypes = {
/** The annotation whose user is relevant */
annotation: propTypes.object.isRequired,
/** services */
features: propTypes.object.isRequired,
serviceUrl: propTypes.func.isRequired,
settings: propTypes.object.isRequired,
};
AnnotationUser.injectedProps = ['features', 'serviceUrl', 'settings'];
module.exports = withServices(AnnotationUser);
'use strict';
const { createElement } = require('preact');
const { shallow } = require('enzyme');
const AnnotationUser = require('../annotation-user');
describe('AnnotationUser', () => {
let fakeAnnotation;
let fakeFeatures;
let fakeIsThirdPartyUser;
let fakeServiceUrl;
let fakeSettings;
let fakeUsername;
const createAnnotationUser = () => {
return shallow(
<AnnotationUser
annotation={fakeAnnotation}
features={fakeFeatures}
serviceUrl={fakeServiceUrl}
settings={fakeSettings}
/>
).dive(); // Dive needed because of `withServices` usage in component
};
beforeEach(() => {
fakeAnnotation = {
user: 'someone@hypothes.is',
};
fakeFeatures = { flagEnabled: sinon.stub() };
fakeIsThirdPartyUser = sinon.stub().returns(false);
fakeServiceUrl = sinon.stub();
fakeSettings = {};
fakeUsername = sinon.stub();
AnnotationUser.$imports.$mock({
'../util/account-id': {
isThirdPartyUser: fakeIsThirdPartyUser,
username: fakeUsername,
},
});
});
afterEach(() => {
AnnotationUser.$imports.$restore();
});
describe('link to user activity', () => {
context('first-party user', () => {
it('should provide a link to the user profile', () => {
fakeIsThirdPartyUser.returns(false);
fakeServiceUrl.returns('link-to-user');
const wrapper = createAnnotationUser();
const linkEl = wrapper.find('a');
assert.isOk(linkEl.exists());
assert.calledWith(fakeServiceUrl, 'user', {
user: fakeAnnotation.user,
});
assert.equal(linkEl.prop('href'), 'link-to-user');
});
});
context('third-party user', () => {
beforeEach(() => {
fakeIsThirdPartyUser.returns(true);
});
it('should link to user if `settings.usernameUrl` is set', () => {
fakeSettings.usernameUrl = 'http://example.com?user=';
fakeUsername.returns('elephant');
const wrapper = createAnnotationUser();
const linkEl = wrapper.find('a');
assert.isOk(linkEl.exists());
assert.equal(linkEl.prop('href'), 'http://example.com?user=elephant');
});
it('should not link to user if `settings.usernameUrl` is not set', () => {
const wrapper = createAnnotationUser();
const linkEl = wrapper.find('a');
assert.isNotOk(linkEl.exists());
});
});
});
describe('rendered user name', () => {
context('feature flag on', () => {
beforeEach(() => {
fakeFeatures.flagEnabled.withArgs('client_display_names').returns(true);
});
it('should render a display name when feature flag on and info available', () => {
fakeAnnotation.user_info = {
display_name: 'Maple Oaks',
};
const wrapper = createAnnotationUser();
const linkEl = wrapper.find('a');
assert.equal(linkEl.text(), 'Maple Oaks');
});
it('should render a username when feature flag on but info not present', () => {
fakeUsername.returns('myusername');
const wrapper = createAnnotationUser();
const linkEl = wrapper.find('a');
assert.equal(linkEl.text(), 'myusername');
});
});
context('feature flag off', () => {
it('should render a username for first-party users when feature flag off', () => {
fakeFeatures.flagEnabled.returns(false);
fakeUsername.returns('myusername');
fakeAnnotation.user_info = {
display_name: 'Maple Oaks',
};
const wrapper = createAnnotationUser();
const linkEl = wrapper.find('a');
assert.equal(linkEl.text(), 'myusername');
});
it('should render a display name for third-party users', () => {
fakeAnnotation.user_info = {
display_name: 'Maple Oaks',
};
fakeIsThirdPartyUser.returns(true);
fakeFeatures.flagEnabled.returns(false);
const wrapper = createAnnotationUser();
assert.equal(wrapper.text(), 'Maple Oaks');
});
});
});
});
...@@ -155,6 +155,10 @@ function startAngularApp(config) { ...@@ -155,6 +155,10 @@ function startAngularApp(config) {
require('./components/annotation-share-dialog') require('./components/annotation-share-dialog')
) )
.component('annotationThread', require('./components/annotation-thread')) .component('annotationThread', require('./components/annotation-thread'))
.component(
'annotationUser',
wrapReactComponent(require('./components/annotation-user'))
)
.component( .component(
'annotationViewerContent', 'annotationViewerContent',
require('./components/annotation-viewer-content') require('./components/annotation-viewer-content')
......
<header class="annotation-header"> <header class="annotation-header">
<!-- User --> <!-- User -->
<span ng-if="vm.user()"> <span ng-if="vm.user()">
<a class="annotation-header__user" <annotation-user annotation="vm.annotation"></annotation-user>
target="_blank"
ng-if="!vm.isThirdPartyUser()"
ng-href="{{vm.serviceUrl('user',{user:vm.user()})}}"
>{{vm.displayName()}}</a>
<a class="annotation-header__user"
target="_blank"
ng-if="vm.isThirdPartyUser() && vm.thirdPartyUsernameLink()"
href="{{ vm.thirdPartyUsernameLink() }}"
>{{vm.displayName()}}</a>
<span class="annotation-header__user"
ng-if="vm.isThirdPartyUser() && !vm.thirdPartyUsernameLink()"
>{{vm.displayName()}}</span>
<span class="annotation-collapsed-replies"> <span class="annotation-collapsed-replies">
<a class="annotation-link" href="" <a class="annotation-link" href=""
ng-click="vm.onReplyCountClick()" ng-click="vm.onReplyCountClick()"
ng-pluralize count="vm.replyCount" ng-pluralize count="vm.replyCount"
when="{'0': '', 'one': '1 reply', 'other': '{} replies'}"></a> when="{'0': '', 'one': '1 reply', 'other': '{} replies'}"></a>
</span> </span>
<br> <br />
<span class="annotation-header__share-info"> <span class="annotation-header__share-info">
<a class="annotation-header__group" <a class="annotation-header__group"
target="_blank" ng-if="vm.group() && vm.group().links.html" href="{{vm.group().links.html}}"> target="_blank" ng-if="vm.group() && vm.group().links.html" href="{{vm.group().links.html}}">
......
.annotation-user,
.annotation-user a {
@include font-normal;
color: $grey-7;
font-weight: bold;
.is-dimmed & {
color: $grey-5;
}
.is-highlighted & {
color: $grey-7;
}
}
...@@ -29,19 +29,17 @@ ...@@ -29,19 +29,17 @@
} }
.annotation.is-dimmed { .annotation.is-dimmed {
// Lighten the username and bodies of dimmed annotations to make other // Lighten the bodies of dimmed annotations to make other
// annotations which are not dimmed stand out // annotations which are not dimmed stand out
.annotation-header__user,
.annotation-body { .annotation-body {
color: $grey-5; color: $grey-5;
} }
} }
.annotation.is-highlighted { .annotation.is-highlighted {
// Slightly darken the username and bodies of highlighted annotations to // Slightly darken the bodies of highlighted annotations to
// make them stand out next to others, which will have the `is-dimmed` state // make them stand out next to others, which will have the `is-dimmed` state
// set // set
.annotation-header__user,
.annotation-body { .annotation-body {
color: $grey-7; color: $grey-7;
} }
...@@ -155,12 +153,6 @@ ...@@ -155,12 +153,6 @@
height: 208px; height: 208px;
} }
.annotation-header__user {
@include font-normal;
color: $grey-7;
font-weight: bold;
}
.annotation-replies { .annotation-replies {
float: left; float: left;
margin-top: 2px; margin-top: 2px;
......
...@@ -22,6 +22,7 @@ $base-line-height: 20px; ...@@ -22,6 +22,7 @@ $base-line-height: 20px;
@import './components/annotation-share-dialog'; @import './components/annotation-share-dialog';
@import './components/annotation-publish-control'; @import './components/annotation-publish-control';
@import './components/annotation-thread'; @import './components/annotation-thread';
@import './components/annotation-user';
@import './components/excerpt'; @import './components/excerpt';
@import './components/group-list'; @import './components/group-list';
@import './components/group-list-item'; @import './components/group-list-item';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment