Unverified Commit cf87bd52 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1195 from hypothesis/remove-old-groups-menu

Remove old groups menu and "community_groups" feature flag checks
parents fe386238 c3269060
'use strict';
const { isThirdPartyUser } = require('../util/account-id');
const isThirdPartyService = require('../util/is-third-party-service');
const serviceConfig = require('../service-config');
const memoize = require('../util/memoize');
const groupsByOrganization = require('../util/group-organizations');
const groupOrganizations = memoize(groupsByOrganization);
// @ngInject
function GroupListController(
$window,
analytics,
features,
groups,
settings,
serviceUrl
) {
this.groups = groups;
this.createNewGroup = function() {
$window.open(serviceUrl('groups.new'), '_blank');
};
this.focusedIcon = function() {
const focusedGroup = this.groups.focused();
return (
focusedGroup &&
(focusedGroup.organization.logo || this.thirdPartyGroupIcon)
);
};
this.focusedIconClass = function() {
const focusedGroup = this.groups.focused();
return focusedGroup && focusedGroup.type === 'private' ? 'group' : 'public';
};
this.isThirdPartyUser = function() {
return isThirdPartyUser(this.auth.userid, settings.authDomain);
};
this.leaveGroup = function(groupId) {
const groupName = groups.get(groupId).name;
const message =
'Are you sure you want to leave the group "' + groupName + '"?';
if ($window.confirm(message)) {
analytics.track(analytics.events.GROUP_LEAVE);
groups.leave(groupId);
}
};
this.orgName = function(groupId) {
const group = this.groups.get(groupId);
return group && group.organization && group.organization.name;
};
this.groupOrganizations = function() {
return groupOrganizations(this.groups.all());
};
this.viewGroupActivity = function() {
analytics.track(analytics.events.GROUP_VIEW_ACTIVITY);
};
this.focusGroup = function(groupId) {
analytics.track(analytics.events.GROUP_SWITCH);
groups.focus(groupId);
};
/**
* Show the share link for the group if it is not a third-party group
* AND if the URL needed is present in the group object. We should be able
* to simplify this once the API is adjusted only to return the link
* when applicable.
*/
this.shouldShowActivityLink = function(groupId) {
const group = groups.get(groupId);
return group.links && group.links.html && !this.isThirdPartyService;
};
const svc = serviceConfig(settings);
if (svc && svc.icon) {
this.thirdPartyGroupIcon = svc.icon;
}
this.isThirdPartyService = isThirdPartyService(settings);
this.showGroupsMenu = () => {
if (features.flagEnabled('community_groups')) {
// Only show the drop down menu if there is more than one group.
return this.groups.all().length > 1;
} else {
return !(this.isThirdPartyService && this.groups.all().length <= 1);
}
};
/**
* Expose the feature flag so it can be used in the template logic to show
* or hide the new groups menu.
*/
this.isFeatureFlagEnabled = flag => {
return features.flagEnabled(flag);
};
}
module.exports = {
controller: GroupListController,
controllerAs: 'vm',
bindings: {
auth: '<',
},
template: require('../templates/group-list.html'),
};
'use strict';
const angular = require('angular');
const immutable = require('seamless-immutable');
const { events } = require('../../services/analytics');
const groupList = require('../group-list');
const util = require('../../directive/test/util');
const groupFixtures = require('../../test/group-fixtures');
describe('groupList', function() {
let $window;
const privateGroup = immutable({
id: 'private',
links: {
html: 'https://hypothes.is/groups/hdevs',
},
name: 'Private',
organization: groupFixtures.defaultOrganization(),
type: 'private',
isScopedToUri: true,
});
const restrictedGroup = immutable({
id: 'restricted',
links: {
html: 'https://hypothes.is/groups/restricto',
},
name: 'Restricted',
organization: groupFixtures.defaultOrganization(),
type: 'restricted',
isScopedToUri: true,
});
const publicGroup = immutable({
id: '__world__',
links: {
html: 'https://hypothes.is/groups/__world__/public',
},
name: 'Public',
organization: groupFixtures.defaultOrganization(),
type: 'open',
isScopedToUri: true,
});
let groups;
let fakeGroups;
let fakeAnalytics;
let fakeServiceUrl;
let fakeSettings;
let fakeFeatures;
before(function() {
angular
.module('app', [])
.component('groupList', groupList)
.factory('groups', function() {
return fakeGroups;
});
});
beforeEach(function() {
fakeFeatures = {
flagEnabled: sinon.stub().returns(false),
};
fakeAnalytics = {
track: sinon.stub(),
events,
};
fakeServiceUrl = sinon.stub();
fakeSettings = {
authDomain: 'example.com',
};
angular.mock.module('app', {
analytics: fakeAnalytics,
serviceUrl: fakeServiceUrl,
settings: fakeSettings,
features: fakeFeatures,
});
});
beforeEach(
angular.mock.inject(function(_$window_) {
$window = _$window_;
groups = [publicGroup, privateGroup, restrictedGroup];
fakeGroups = {
all: function() {
return groups;
},
get: function(id) {
const match = this.all().filter(function(group) {
return group.id === id;
});
return match.length > 0 ? match[0] : undefined;
},
leave: sinon.stub(),
focus: sinon.stub(),
focused: sinon.stub(),
};
})
);
function createGroupList({ userid } = { userid: 'acct:person@example.com' }) {
return util.createDirective(document, 'groupList', {
auth: {
status: userid ? 'logged-in' : 'logged-out',
userid,
},
});
}
it('should render groups', function() {
const element = createGroupList();
const groupItems = element.find('.group-item');
assert.equal(groupItems.length, groups.length + 1);
});
it('should render appropriate group name link title per group type', function() {
const element = createGroupList();
const nameLinks = element.find('.group-name-link');
assert.equal(nameLinks.length, groups.length + 1);
assert.include(nameLinks[0].title, 'Show public annotations'); // Open
assert.include(nameLinks[1].title, 'Show and create annotations in'); // Private
assert.include(nameLinks[2].title, 'Show public annotations'); // Restricted
});
it('should render organization logo for focused group', function() {
const org = groupFixtures.organization({
logo: 'http://www.example.com/foobar',
});
const group = groupFixtures.expandedGroup({
organization: org,
});
fakeGroups.focused = () => {
return group;
};
const element = createGroupList();
const imgEl = element.find('.dropdown-toggle > img.group-list-label__icon');
assert.equal(imgEl[0].src, org.logo);
});
it('should render fallback icon for focused group when no logo (private)', function() {
const org = groupFixtures.organization({ logo: null });
const group = groupFixtures.expandedGroup({
organization: org,
type: 'private',
});
fakeGroups.focused = () => {
return group;
};
const element = createGroupList();
const iconEl = element.find('.dropdown-toggle > i.h-icon-group');
assert.ok(iconEl[0]);
});
it('should render fallback icon for focused group when no logo (restricted)', function() {
const org = groupFixtures.organization({ logo: null });
const group = groupFixtures.expandedGroup({
organization: org,
type: 'restricted',
});
fakeGroups.focused = () => {
return group;
};
const element = createGroupList();
const iconEl = element.find('.dropdown-toggle > i.h-icon-public');
assert.ok(iconEl[0]);
});
it('should render fallback icon for focused group when no logo (open)', function() {
const org = groupFixtures.organization({ logo: null });
const group = groupFixtures.expandedGroup({
organization: org,
type: 'open',
});
fakeGroups.focused = () => {
return group;
};
const element = createGroupList();
const iconEl = element.find('.dropdown-toggle > i.h-icon-public');
assert.ok(iconEl[0]);
});
it('should render organization icons for first group in each organization', function() {
const orgs = [
groupFixtures.defaultOrganization(),
groupFixtures.organization(),
];
groups = [
groupFixtures.expandedGroup({ organization: orgs[0] }),
groupFixtures.expandedGroup({ organization: orgs[0] }),
groupFixtures.expandedGroup({ organization: orgs[1] }),
groupFixtures.expandedGroup({ organization: orgs[1] }),
];
const element = createGroupList();
const iconContainers = element.find('.group-menu-icon-container');
const iconImages = element.find('.group-menu-icon-container > img');
assert.lengthOf(iconContainers, groups.length);
assert.lengthOf(iconImages, orgs.length);
});
it('should not render organization icons for menu groups if missing', function() {
const orgs = [
groupFixtures.organization({ logo: null }),
groupFixtures.organization({ logo: null }),
];
groups = [
groupFixtures.expandedGroup({ organization: orgs[0] }),
groupFixtures.expandedGroup({ organization: orgs[0] }),
groupFixtures.expandedGroup({ organization: orgs[1] }),
groupFixtures.expandedGroup({ organization: orgs[1] }),
];
const element = createGroupList();
const iconContainers = element.find('.group-menu-icon-container');
const iconImages = element.find('.group-menu-icon-container > img');
assert.lengthOf(iconContainers, groups.length);
assert.lengthOf(iconImages, 0);
});
it('should render share links', function() {
const element = createGroupList();
const shareLinks = element.find('.share-link-container');
assert.equal(shareLinks.length, groups.length);
const link = element.find('.share-link');
assert.equal(link.length, groups.length);
assert.equal(link[0].href, publicGroup.links.html);
assert.equal(link[1].href, privateGroup.links.html);
assert.equal(link[2].href, restrictedGroup.links.html);
});
it('should not render share links if they are not present', function() {
groups = [
{
type: 'private',
},
{
id: 'anOpenGroup',
type: 'open',
links: {},
},
];
const element = createGroupList();
const links = element.find('.share-link-container');
assert.equal(links.length, 0);
});
[
{
// Logged-in third party user.
firstPartyAuthDomain: 'example.com',
authDomain: 'publisher.org',
userid: 'acct:person@publisher.org',
},
{
// Logged-out third party user.
firstPartyAuthDomain: 'example.com',
authDomain: 'publisher.org',
userid: null,
},
].forEach(({ firstPartyAuthDomain, authDomain, userid }) => {
it('should not render share links for third-party groups', () => {
fakeSettings.authDomain = firstPartyAuthDomain;
fakeSettings.services = [
{
authority: authDomain,
},
];
const element = createGroupList({ userid });
const shareLinks = element.find('.share-link-container');
assert.equal(shareLinks.length, 0);
});
});
it('should track metrics when a user attempts to view a groups activity', function() {
const element = createGroupList();
const link = element.find('.share-link');
link.click();
assert.calledWith(
fakeAnalytics.track,
fakeAnalytics.events.GROUP_VIEW_ACTIVITY
);
});
function clickLeaveIcon(element, acceptPrompt) {
const leaveLink = element.find('.h-icon-cancel-outline');
// accept prompt to leave group
$window.confirm = function() {
return acceptPrompt;
};
leaveLink.click();
}
it('should leave group when the leave icon is clicked', function() {
const element = createGroupList();
clickLeaveIcon(element, true);
assert.ok(fakeGroups.leave.calledWith(privateGroup.id));
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.GROUP_LEAVE);
});
it('should not leave group when confirmation is dismissed', function() {
const element = createGroupList();
clickLeaveIcon(element, false);
assert.notCalled(fakeGroups.leave);
assert.notCalled(fakeAnalytics.track);
});
it('should not change the focused group when leaving', function() {
const element = createGroupList();
clickLeaveIcon(element, true);
assert.notCalled(fakeGroups.focus);
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.GROUP_LEAVE);
});
it('should change current group focus when click another group', function() {
const element = createGroupList();
const groupItems = element.find('.group-item');
// click the second group
groupItems[1].click();
assert.calledOnce(fakeGroups.focus);
assert.calledWith(fakeAnalytics.track, fakeAnalytics.events.GROUP_SWITCH);
});
it('should open a window when "New Group" is clicked', function() {
fakeServiceUrl
.withArgs('groups.new')
.returns('https://test.hypothes.is/groups/new');
const element = createGroupList();
$window.open = sinon.stub();
const newGroupLink = element[0].querySelector('.new-group-btn a');
angular.element(newGroupLink).click();
assert.calledWith(
$window.open,
'https://test.hypothes.is/groups/new',
'_blank'
);
});
describe('group menu visibility', () => {
it('is hidden when third party service and only one group', function() {
// Configure third party service.
fakeSettings.authDomain = 'example.com';
fakeSettings.services = [
{
authority: 'publisher.org',
},
];
// Configure only one group.
groups = [privateGroup];
const element = createGroupList();
const showGroupsMenu = element.ctrl.showGroupsMenu();
const dropdownToggle = element.find('.dropdown-toggle');
const arrowIcon = element.find('.h-icon-arrow-drop-down');
const dropdownMenu = element.find('.dropdown-menu__top-arrow');
const dropdownOptions = element.find('.dropdown-menu__row');
assert.isFalse(showGroupsMenu);
assert.lengthOf(dropdownToggle, 0);
assert.lengthOf(arrowIcon, 0);
assert.lengthOf(dropdownMenu, 0);
assert.lengthOf(dropdownOptions, 0);
});
it('is shown when there is more than one group', function() {
// Configure third party service.
fakeSettings.authDomain = 'example.com';
fakeSettings.services = [
{
authority: 'publisher.org',
},
];
const element = createGroupList();
const showGroupsMenu = element.ctrl.showGroupsMenu();
const dropdownToggle = element.find('.dropdown-toggle');
const arrowIcon = element.find('.h-icon-arrow-drop-down');
const dropdownMenu = element.find('.dropdown-menu__top-arrow');
const dropdownOptions = element.find('.dropdown-menu__row');
assert.isTrue(showGroupsMenu);
assert.lengthOf(dropdownToggle, 1);
assert.lengthOf(arrowIcon, 1);
assert.lengthOf(dropdownMenu, 1);
assert.lengthOf(dropdownOptions, 4);
});
it('is shown when it is not a third party service', function() {
// Configure only one group.
groups = [privateGroup];
const element = createGroupList();
const showGroupsMenu = element.ctrl.showGroupsMenu();
const dropdownToggle = element.find('.dropdown-toggle');
const arrowIcon = element.find('.h-icon-arrow-drop-down');
const dropdownMenu = element.find('.dropdown-menu__top-arrow');
const dropdownOptions = element.find('.dropdown-menu__row');
assert.isTrue(showGroupsMenu);
assert.lengthOf(dropdownToggle, 1);
assert.lengthOf(arrowIcon, 1);
assert.lengthOf(dropdownMenu, 1);
assert.lengthOf(dropdownOptions, 2);
});
});
[false, true].forEach(isEnabled => {
it('returns what features.flagEnabled returns', function() {
fakeFeatures.flagEnabled.withArgs('community_groups').returns(isEnabled);
const element = createGroupList();
const communityGroupsEnabled = element.ctrl.isFeatureFlagEnabled(
'community_groups'
);
assert.isTrue(communityGroupsEnabled === isEnabled);
});
});
});
...@@ -161,9 +161,8 @@ function startAngularApp(config) { ...@@ -161,9 +161,8 @@ function startAngularApp(config) {
require('./components/annotation-viewer-content') require('./components/annotation-viewer-content')
) )
.component('excerpt', require('./components/excerpt')) .component('excerpt', require('./components/excerpt'))
.component('groupList', require('./components/group-list'))
.component( .component(
'groupListV2', 'groupList',
wrapReactComponent(require('./components/group-list-v2')) wrapReactComponent(require('./components/group-list-v2'))
) )
.component( .component(
......
...@@ -16,7 +16,6 @@ const DEFAULT_ORGANIZATION = { ...@@ -16,7 +16,6 @@ const DEFAULT_ORGANIZATION = {
const events = require('../events'); const events = require('../events');
const { awaitStateChange } = require('../util/state-util'); const { awaitStateChange } = require('../util/state-util');
const { combineGroups } = require('../util/groups'); const { combineGroups } = require('../util/groups');
const memoize = require('../util/memoize');
const serviceConfig = require('../service-config'); const serviceConfig = require('../service-config');
// @ngInject // @ngInject
...@@ -29,8 +28,7 @@ function groups( ...@@ -29,8 +28,7 @@ function groups(
serviceUrl, serviceUrl,
session, session,
settings, settings,
auth, auth
features
) { ) {
const svc = serviceConfig(settings); const svc = serviceConfig(settings);
const authority = svc ? svc.authority : null; const authority = svc ? svc.authority : null;
...@@ -299,25 +297,8 @@ function groups( ...@@ -299,25 +297,8 @@ function groups(
return groups; return groups;
} }
const sortGroups = memoize(groups => {
// Sort in the following order: scoped, public, private.
// This is for maintaining the order of the old groups menu so when
// the old groups menu is removed this can be removed.
const worldGroups = groups.filter(g => g.id === '__world__');
const nonWorldScopedGroups = groups.filter(
g => g.id !== '__world__' && ['open', 'restricted'].includes(g.type)
);
const remainingGroups = groups.filter(
g => !worldGroups.includes(g) && !nonWorldScopedGroups.includes(g)
);
return nonWorldScopedGroups.concat(worldGroups).concat(remainingGroups);
});
function all() { function all() {
if (features.flagEnabled('community_groups')) { return store.allGroups();
return store.allGroups();
}
return sortGroups(store.getInScopeGroups());
} }
// Return the full object for the group with the given id. // Return the full object for the group with the given id.
......
...@@ -41,7 +41,6 @@ const dummyGroups = [ ...@@ -41,7 +41,6 @@ const dummyGroups = [
describe('groups', function() { describe('groups', function() {
let fakeAuth; let fakeAuth;
let fakeFeatures;
let fakeStore; let fakeStore;
let fakeIsSidebar; let fakeIsSidebar;
let fakeSession; let fakeSession;
...@@ -55,9 +54,6 @@ describe('groups', function() { ...@@ -55,9 +54,6 @@ describe('groups', function() {
fakeAuth = { fakeAuth = {
tokenGetter: sinon.stub().returns('1234'), tokenGetter: sinon.stub().returns('1234'),
}; };
fakeFeatures = {
flagEnabled: sinon.stub().returns(false),
};
fakeStore = fakeReduxStore( fakeStore = fakeReduxStore(
{ {
...@@ -146,42 +142,17 @@ describe('groups', function() { ...@@ -146,42 +142,17 @@ describe('groups', function() {
fakeServiceUrl, fakeServiceUrl,
fakeSession, fakeSession,
fakeSettings, fakeSettings,
fakeAuth, fakeAuth
fakeFeatures
); );
} }
describe('#all', function() { describe('#all', function() {
it('returns all groups from store.allGroups when community-groups feature flag is enabled', () => { it('returns all groups from store.allGroups', () => {
const svc = service(); const svc = service();
fakeStore.allGroups = sinon.stub().returns(dummyGroups); fakeStore.allGroups = sinon.stub().returns(dummyGroups);
fakeFeatures.flagEnabled.withArgs('community_groups').returns(true);
assert.deepEqual(svc.all(), dummyGroups); assert.deepEqual(svc.all(), dummyGroups);
assert.called(fakeStore.allGroups); assert.called(fakeStore.allGroups);
}); });
it('returns all groups from store.getInScopeGroups when community-groups feature flag is disabled', () => {
const svc = service();
fakeStore.getInScopeGroups = sinon.stub().returns(dummyGroups);
assert.deepEqual(svc.all(), dummyGroups);
assert.called(fakeStore.getInScopeGroups);
});
[[0, 1, 2, 3], [2, 0, 1, 3], [0, 3, 1, 2]].forEach(groupInputOrder => {
it('sorts the groups in the following order: scoped, public, private maintaining order within each category.', () => {
const groups = [
{ id: 0, type: 'open' },
{ id: 1, type: 'restricted' },
{ id: '__world__', type: 'open' },
{ id: 3, type: 'private' },
];
const svc = service();
fakeStore.getInScopeGroups = sinon
.stub()
.returns(groupInputOrder.map(id => groups[id]));
assert.deepEqual(svc.all(), groups);
});
});
}); });
describe('#load', function() { describe('#load', function() {
......
<div
class="pull-right"
dropdown
keyboard-nav
ng-if="!vm.isFeatureFlagEnabled('community_groups')"
>
<!-- Show a drop down menu if showGroupsMenu is true. -->
<div
class="dropdown-toggle"
dropdown-toggle
data-toggle="dropdown"
role="button"
tabindex="0"
ng-if="vm.showGroupsMenu()"
>
<img
class="group-list-label__icon group-list-label__icon--organization"
ng-src="{{ vm.focusedIcon() }}"
alt="{{ vm.orgName(vm.groups.focused().id)}}"
ng-if="vm.focusedIcon()"
/>
<i
class="group-list-label__icon h-icon-{{ vm.focusedIconClass() }}"
ng-if="!vm.focusedIcon()"
></i
><!-- nospace
!--><span class="group-list-label__label"
><a class="group-list-label__toggle">{{vm.groups.focused().name}}</a
><!-- nospace
!--><i class="h-icon-arrow-drop-down"></i
></span>
</div>
<!-- Do not show a drop down menu if showGroupsMenu is false. -->
<div ng-if="!vm.showGroupsMenu()">
<img
class="group-list-label__icon group-list-label__icon--organization"
ng-src="{{ vm.focusedIcon() }}"
alt="{{ vm.orgName(vm.groups.focused().id)}}"
ng-if="vm.focusedIcon()"
/>
<i
class="group-list-label__icon h-icon-{{ vm.focusedIconClass() }}"
ng-if="!vm.focusedIcon()"
></i
><!-- nospace
!--><span class="group-list-label__label"
>{{vm.groups.focused().name}}</span
>
</div>
<!-- Only build the drop down menu if showGroupsMenu is true. -->
<!-- Original groups menu. -->
<div
class="dropdown-menu__top-arrow"
ng-if="vm.showGroupsMenu() && !vm.isFeatureFlagEnabled('community_groups')"
></div>
<ul
class="dropdown-menu pull-none"
role="menu"
ng-if="vm.showGroupsMenu() && !vm.isFeatureFlagEnabled('community_groups')"
>
<li
class="dropdown-menu__row dropdown-menu__row--unpadded "
ng-repeat="group in vm.groupOrganizations() track by group.id"
>
<div
ng-class="{'group-item': true, selected: group.id == vm.groups.focused().id}"
ng-click="vm.focusGroup(group.id)"
>
<!-- the group icon !-->
<div class="group-menu-icon-container">
<img
class="group-list-label__icon group-list-label__icon--organization"
alt="{{ vm.orgName(group.id) }}"
ng-src="{{ group.logo }}"
ng-if="group.logo"
/>
</div>
<!-- the group name and share link !-->
<div class="group-details">
<div class="group-name-container">
<a
class="group-name-link"
href=""
title="{{ group.type === 'private' ? 'Show and create annotations in ' + group.name : 'Show public annotations' }}"
>
{{group.name}}
</a>
</div>
<div
class="share-link-container"
ng-click="$event.stopPropagation()"
ng-if="vm.shouldShowActivityLink(group.id)"
>
<a
class="share-link"
href="{{group.links.html}}"
target="_blank"
ng-click="vm.viewGroupActivity()"
>
View group activity
<span ng-if="group.type === 'private'">
and invite others
</span>
</a>
</div>
</div>
<!-- the 'Leave group' icon !-->
<div
class="group-cancel-icon-container"
ng-click="$event.stopPropagation()"
>
<i
class="h-icon-cancel-outline btn--cancel"
ng-if="group.type === 'private'"
ng-click="vm.leaveGroup(group.id)"
title="Leave '{{group.name}}'"
></i>
</div>
</div>
</li>
<li
ng-if="vm.auth.status === 'logged-in' && !vm.isThirdPartyUser()"
class="dropdown-menu__row dropdown-menu__row--unpadded new-group-btn"
>
<div class="group-item" ng-click="vm.createNewGroup()">
<div class="group-icon-container"><i class="h-icon-add"></i></div>
<div class="group-details">
<a
href=""
class="group-name-link"
title="Create a new group to share annotations"
>
New private group
</a>
</div>
</div>
</li>
</ul>
</div>
<group-list-v2
ng-if="vm.isFeatureFlagEnabled('community_groups')"
></group-list-v2>
/* The group-list out of scope item. */
.group-list-item-out-of-scope {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.group-list-item-out-of-scope__item {
background-color: $gray-lightest;
color: $gray-light;
}
.group-list-item-out-of-scope__icon--unavailable {
fill: $gray-light;
float: right;
height: 20px;
width: auto;
}
.group-list-item-out-of-scope__details {
flex-grow: 1;
flex-shrink: 1;
font-weight: 500;
}
.group-list-item-out-of-scope__details-toggle {
font-size: $body1-font-size;
font-style: italic;
margin: 0;
text-decoration: underline;
}
.group-list-item-out-of-scope__details-unavailable-message {
font-size: $body1-font-size;
line-height: 1.5;
white-space: normal;
width: $group-list-width - 60px;
}
.group-list-item-out-of-scope__details-actions {
text-align: right;
}
.group-list-item-out-of-scope__details-group-page-link {
color: inherit;
font-size: $body1-font-size;
text-decoration: underline;
text-transform: uppercase;
}
/* The groups dropdown list. */
$group-list-spacing-below: 50px;
.group-list {
.dropdown {
white-space: nowrap;
}
.dropdown-menu {
width: $group-list-width;
max-height: 500px; // fallback for browsers lacking support for vh/calc
max-height: calc(100vh - #{$top-bar-height} - #{$group-list-spacing-below});
overflow-y: auto;
.group-name {
overflow: hidden;
text-overflow: ellipsis;
width: $group-list-width - 30px;
}
}
.dropdown-menu__section--no-header {
.group-details {
font-weight: 400;
}
}
.group-item {
display: flex;
flex-direction: row;
flex-grow: 1;
padding: 10px;
cursor: pointer;
&:hover {
.group-name-link {
color: $brand-color;
}
}
&.selected {
.group-name-link {
font-size: $body2-font-size;
font-weight: 600;
}
}
}
.group-icon-container {
margin-right: 10px;
}
.group-menu-icon-container {
margin-right: 10px;
width: 15px;
height: 15px;
}
.group-cancel-icon-container {
// the 'Leave group' icon is shifted down slightly
// so that it lines up vertically with the 'chat heads' icon on the
// left-hand side of the groups list
padding-top: 3px;
margin-right: 2px;
}
.group-details {
flex-grow: 1;
flex-shrink: 1;
}
.new-group-btn {
background-color: $gray-lightest;
.group-item {
padding-top: 12px;
padding-bottom: 12px;
}
.h-icon-add {
font-weight: bold;
}
}
}
// the icon indicating the type of group currently selected at
// the top of the groups list
.group-list-label__icon {
color: $color-gray;
display: inline-block;
margin-right: 4px;
position: relative;
vertical-align: baseline;
// align the base of the chat-heads icon for groups
// with the baseline of the group name label
transform: translateY(1px);
}
.group-list-label__icon--organization {
height: 15px;
width: 15px;
top: 2px;
}
// the label showing the currently selected group which opens
// the drop-down list when clicked
.group-list-label__label {
font-size: $body2-font-size;
font-weight: bold;
display: inline-block;
}
// the anchor showing the name of the currently selected group
.group-list-label__toggle {
color: $gray-dark;
}
// the name of a group in the groups drop-down list
// and 'Post to <Group>' button for saving annotations
.group-name-link {
white-space: nowrap;
color: inherit;
}
.open {
& > .group-list__toggle {
background: $gray-lighter;
}
}
...@@ -23,10 +23,8 @@ $base-line-height: 20px; ...@@ -23,10 +23,8 @@ $base-line-height: 20px;
@import './components/annotation-publish-control'; @import './components/annotation-publish-control';
@import './components/annotation-thread'; @import './components/annotation-thread';
@import './components/excerpt'; @import './components/excerpt';
@import './components/group-list';
@import './components/group-list-v2'; @import './components/group-list-v2';
@import './components/group-list-item'; @import './components/group-list-item';
@import './components/group-list-item-out-of-scope';
@import './components/help-panel'; @import './components/help-panel';
@import './components/loggedout-message'; @import './components/loggedout-message';
@import './components/login-control'; @import './components/login-control';
......
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