Commit 396fce0e authored by Sheetal Umesh Kumar's avatar Sheetal Umesh Kumar Committed by Sheetal Umesh Kumar

Implement new customisation options for the sidebar’s display.

See https://github.com/hypothesis/product-backlog/issues/350

Add config options for the following:

disableToolbarCloseBtn: false/true
Disables and hides the close button on the toolbar

disableToolbarMinimizeBtn: true/false
Disables and hides the minimize button on the toolbar

disableToolbarHighlightsBtn: true/false
Disables and hides the highlights button on the toolbar

disableToolbarNewNoteBtn: true/false
Disables and hides the new note button on the toolbar

disableBucketBar: true/false
Disables and hides the bucket bar

enableSidebarDropShadow: true/false
Turns on the dropshadow for the sidebar

theme: 'clean'
Turns on the 'clean' theme for the sidebar. By default the classic view(with annotation cards) is turned on, which the rest of the Hypothesis users will see.

usernameUrl: 'someurl/'
The url to direct a user to when their username in the annotation is clicked.
parent 05dc0a5d
......@@ -18,6 +18,14 @@ function configFrom(window_) {
// URL of the client's boot script. Used when injecting the client into
// child iframes.
clientUrl: settings.clientUrl,
disableToolbarCloseBtn:settings.hostPageSetting('disableToolbarCloseBtn', {defaultValue: true}),
disableToolbarMinimizeBtn: settings.hostPageSetting('disableToolbarMinimizeBtn'),
disableToolbarHighlightsBtn: settings.hostPageSetting('disableToolbarHighlightsBtn'),
disableToolbarNewNoteBtn: settings.hostPageSetting('disableToolbarNewNoteBtn'),
disableBucketBar: settings.hostPageSetting('disableBucketBar'),
enableSidebarDropShadow: settings.hostPageSetting('enableSidebarDropShadow'),
theme: settings.hostPageSetting('theme'),
usernameUrl: settings.hostPageSetting('usernameUrl'),
// Temporary feature flag override for 1st-party OAuth
oauthEnabled: settings.hostPageSetting('oauthEnabled'),
onLayoutChange: settings.hostPageSetting('onLayoutChange'),
......
......@@ -152,6 +152,10 @@ function settingsFrom(window_) {
return jsonConfigs[name];
}
if (typeof options.defaultValue !== 'undefined') {
return options.defaultValue;
}
return null;
}
......
......@@ -487,14 +487,6 @@ describe('annotator.config.settingsFrom', function() {
jsonSettings: {foo: 'jsonValue'},
expected: undefined,
},
{
when: 'the client is embedded in a web page',
specify: "it returns null if the setting isn't defined anywhere",
isBrowserExtension: false,
configFuncSettings: {},
jsonSettings: {},
expected: null,
},
{
when: 'the client is in a browser extension',
specify: 'it always returns null',
......@@ -521,6 +513,26 @@ describe('annotator.config.settingsFrom', function() {
jsonSettings: {foo: 'jsonValue'},
expected: 'jsonValue',
},
{
when: 'the defaultValue is null',
specify: 'it returns null',
isBrowserExtension: false,
allowInBrowserExt: false,
configFuncSettings: {},
jsonSettings: {},
defaultValue: null,
expected: null,
},
{
when: 'the defaultValue is specified',
specify: 'it returns that default value',
isBrowserExtension: false,
allowInBrowserExt: false,
configFuncSettings: {},
jsonSettings: {},
defaultValue: 'test value',
expected: 'test value',
},
].forEach(function(test) {
context(test.when, function() {
specify(test.specify, function() {
......@@ -531,7 +543,10 @@ describe('annotator.config.settingsFrom', function() {
var setting = settings.hostPageSetting(
'foo',
{allowInBrowserExt: test.allowInBrowserExt || false}
{
allowInBrowserExt: test.allowInBrowserExt || false,
defaultValue: test.defaultValue || null,
}
);
assert.strictEqual(setting, test.expected);
......
......@@ -44,7 +44,11 @@ module.exports = class Host extends Guest
@frame = $('<div></div>')
.css('display', 'none')
.addClass('annotator-frame annotator-outer')
.appendTo(element)
if config.enableSidebarDropShadow
@frame.addClass('annotator-frame--drop-shadow-enabled')
@frame.appendTo(element)
super
......
......@@ -59,6 +59,10 @@ $.noConflict(true)(function() {
window.__hypothesis_frame = true;
}
if(config.disableBucketBar) {
delete pluginClasses.BucketBar;
}
config.pluginClasses = pluginClasses;
var annotator = new Klass(document.body, config);
......
......@@ -28,6 +28,16 @@ module.exports = class Toolbar extends Plugin
$(@element).append @toolbar
items = [
"title": "Close Sidebar"
"class": "annotator-frame-button--sidebar_close h-icon-close"
"name": "sidebar-close"
"on":
"click": (event) =>
event.preventDefault()
event.stopPropagation()
@annotator.hide()
@toolbar.find('[name=sidebar-close]').hide();
,
"title": "Toggle or Resize Sidebar"
"class": "annotator-frame-button--sidebar_toggle h-icon-chevron-left"
"name": "sidebar-toggle"
......@@ -83,3 +93,34 @@ module.exports = class Toolbar extends Plugin
.removeClass('h-icon-visibility')
.addClass('h-icon-visibility-off')
.prop('title', 'Show Highlights');
disableMinimizeBtn: () ->
$('[name=sidebar-toggle]').remove();
disableHighlightsBtn: () ->
$('[name=highlight-visibility]').remove();
disableNewNoteBtn: () ->
$('[name=insert-comment]').remove();
disableCloseBtn: () ->
$('[name=sidebar-close]').remove();
getWidth: () ->
return parseInt(window.getComputedStyle(this.toolbar[0]).width)
hideCloseBtn: () ->
$('[name=sidebar-close]').hide();
showCloseBtn: () ->
$('[name=sidebar-close]').show();
showCollapseSidebarBtn: () ->
$('[name=sidebar-toggle]')
.removeClass('h-icon-chevron-left')
.addClass('h-icon-chevron-right')
showExpandSidebarBtn: () ->
$('[name=sidebar-toggle]')
.removeClass('h-icon-chevron-right')
.addClass('h-icon-chevron-left')
......@@ -35,7 +35,17 @@ module.exports = class Sidebar extends Host
@plugins.BucketBar.element.on 'click', (event) => this.show()
if @plugins.Toolbar?
@toolbarWidth = parseInt(window.getComputedStyle(this.plugins.Toolbar.toolbar[0]).width)
@toolbarWidth = @plugins.Toolbar.getWidth()
if config.disableToolbarMinimizeBtn
@plugins.Toolbar.disableMinimizeBtn()
if config.disableToolbarHighlightsBtn
@plugins.Toolbar.disableHighlightsBtn()
if config.disableToolbarNewNoteBtn
@plugins.Toolbar.disableNewNoteBtn()
if config.disableToolbarCloseBtn
@plugins.Toolbar.disableCloseBtn()
this._setupGestures()
# The partner-provided callback functions.
......@@ -87,24 +97,25 @@ module.exports = class Sidebar extends Host
_setupGestures: ->
$toggle = @toolbar.find('[name=sidebar-toggle]')
# Prevent any default gestures on the handle
$toggle.on('touchmove', (event) -> event.preventDefault())
if $toggle[0]
# Prevent any default gestures on the handle
$toggle.on('touchmove', (event) -> event.preventDefault())
# Set up the Hammer instance and handlers
mgr = new Hammer.Manager($toggle[0])
.on('panstart panend panleft panright', this.onPan)
.on('swipeleft swiperight', this.onSwipe)
# Set up the Hammer instance and handlers
mgr = new Hammer.Manager($toggle[0])
.on('panstart panend panleft panright', this.onPan)
.on('swipeleft swiperight', this.onSwipe)
# Set up the gesture recognition
pan = mgr.add(new Hammer.Pan({direction: Hammer.DIRECTION_HORIZONTAL}))
swipe = mgr.add(new Hammer.Swipe({direction: Hammer.DIRECTION_HORIZONTAL}))
swipe.recognizeWith(pan)
# Set up the gesture recognition
pan = mgr.add(new Hammer.Pan({direction: Hammer.DIRECTION_HORIZONTAL}))
swipe = mgr.add(new Hammer.Swipe({direction: Hammer.DIRECTION_HORIZONTAL}))
swipe.recognizeWith(pan)
# Set up the initial state
this._initializeGestureState()
# Set up the initial state
this._initializeGestureState()
# Return this for chaining
this
# Return this for chaining
this
_initializeGestureState: ->
@gestureState =
......@@ -230,10 +241,10 @@ module.exports = class Sidebar extends Host
@frame.css 'margin-left': "#{-1 * @frame.width()}px"
@frame.removeClass 'annotator-collapsed'
if @toolbar?
@toolbar.find('[name=sidebar-toggle]')
.removeClass('h-icon-chevron-left')
.addClass('h-icon-chevron-right')
if @plugins.Toolbar?
@plugins.Toolbar.showCollapseSidebarBtn();
@plugins.Toolbar.showCloseBtn();
if @options.showHighlights == 'whenSidebarOpen'
@setVisibleHighlights(true)
......@@ -244,10 +255,10 @@ module.exports = class Sidebar extends Host
@frame.css 'margin-left': ''
@frame.addClass 'annotator-collapsed'
if @toolbar?
@toolbar.find('[name=sidebar-toggle]')
.removeClass('h-icon-chevron-right')
.addClass('h-icon-chevron-left')
@plugins.Toolbar.hideCloseBtn();
if @plugins.Toolbar?
@plugins.Toolbar.showExpandSidebarBtn();
if @options.showHighlights == 'whenSidebarOpen'
@setVisibleHighlights(false)
......
......@@ -80,3 +80,7 @@ describe 'Host', ->
host = createHost({annotations: '1234'})
configStr = encodeURIComponent(JSON.stringify({annotations: '1234'}))
assert.equal(host.frame[0].children[0].src, appURL + '?config=' + configStr)
it 'adds drop shadow if in enableSidebarDropShadow', ->
host = createHost({enableSidebarDropShadow: true})
assert.isTrue(host.frame.hasClass('annotator-frame--drop-shadow-enabled'))
......@@ -19,15 +19,34 @@ describe 'Sidebar', ->
return new Sidebar(element, config)
beforeEach ->
sandbox.stub(Sidebar.prototype, '_setupGestures')
fakeCrossFrame = {}
fakeCrossFrame.onConnect = sandbox.stub().returns(fakeCrossFrame)
fakeCrossFrame.on = sandbox.stub().returns(fakeCrossFrame)
fakeCrossFrame.call = sandbox.spy()
fakeCrossFrame.destroy = sandbox.stub()
fakeToolbar = {}
fakeToolbar.disableMinimizeBtn = sandbox.spy()
fakeToolbar.disableHighlightsBtn = sandbox.spy()
fakeToolbar.disableNewNoteBtn = sandbox.spy()
fakeToolbar.disableCloseBtn = sandbox.spy()
fakeToolbar.hideCloseBtn = sandbox.spy()
fakeToolbar.showCloseBtn = sandbox.spy()
fakeToolbar.showExpandSidebarBtn = sandbox.spy()
fakeToolbar.showCollapseSidebarBtn = sandbox.spy()
fakeToolbar.getWidth = sandbox.stub()
fakeToolbar.destroy = sandbox.stub()
CrossFrame = sandbox.stub()
CrossFrame.returns(fakeCrossFrame)
Toolbar = sandbox.stub()
Toolbar.returns(fakeToolbar)
sidebarConfig.pluginClasses['CrossFrame'] = CrossFrame
sidebarConfig.pluginClasses['Toolbar'] = Toolbar
afterEach ->
sandbox.restore()
......@@ -257,6 +276,29 @@ describe 'Sidebar', ->
assert.calledWith(fakeCrossFrame.call, 'setVisibleHighlights', true)
assert.calledWith(sidebar.publish, 'setVisibleHighlights', true)
context 'Hide toolbar buttons', ->
it 'disables minimize btn', ->
sidebar = createSidebar(config={disableToolbarMinimizeBtn: true})
assert.called(sidebar.plugins.Toolbar.disableMinimizeBtn)
it 'disables minimize btn', ->
sidebar = createSidebar(config={disableToolbarHighlightsBtn: true})
assert.called(sidebar.plugins.Toolbar.disableHighlightsBtn)
it 'disables minimize btn', ->
sidebar = createSidebar(config={disableToolbarNewNoteBtn: true})
assert.called(sidebar.plugins.Toolbar.disableNewNoteBtn)
it 'disables minimize btn', ->
sidebar = createSidebar(config={disableToolbarCloseBtn: true})
assert.called(sidebar.plugins.Toolbar.disableCloseBtn)
describe 'layout change notifier', ->
layoutChangeHandlerSpy = null
......
......@@ -182,6 +182,7 @@ module.exports = angular.module('h', [
.component('loginForm', require('./components/login-form'))
.component('markdown', require('./components/markdown'))
.component('moderationBanner', require('./components/moderation-banner'))
.component('newNoteBtn', require('./components/new-note-btn'))
.component('publishAnnotationBtn', require('./components/publish-annotation-btn'))
.component('searchInput', require('./components/search-input'))
.component('searchStatusBar', require('./components/search-status-bar'))
......
......@@ -24,6 +24,12 @@ function AnnotationHeaderController(groups, settings, serviceUrl) {
return persona.isThirdPartyUser(self.annotation.user, settings.authDomain);
};
this.thirdPartyUsernameLink = function () {
return settings.usernameUrl ?
settings.usernameUrl + persona.username(this.annotation.user):
null;
};
this.serviceUrl = serviceUrl;
this.group = function () {
......
'use strict';
var events = require('../events');
module.exports = {
controllerAs: 'vm',
//@ngInject
controller: function ($rootScope, annotationUI) {
this.onNewNoteBtnClick = function(){
var topLevelFrame = annotationUI.frames().find(f=>!f.id);
var annot = {
target: [],
uri: topLevelFrame.uri,
};
$rootScope.$broadcast(events.BEFORE_ANNOTATION_CREATED, annot);
};
},
bindings: {
},
template: require('../templates/new-note-btn.html'),
};
......@@ -5,11 +5,13 @@ var uiConstants = require('../ui-constants');
module.exports = {
controllerAs: 'vm',
//@ngInject
controller: function ($element, annotationUI, features) {
controller: function ($element, annotationUI, features, settings) {
this.TAB_ANNOTATIONS = uiConstants.TAB_ANNOTATIONS;
this.TAB_NOTES = uiConstants.TAB_NOTES;
this.TAB_ORPHANS = uiConstants.TAB_ORPHANS;
this.isThemeClean = settings.theme === 'clean';
this.selectTab = function (type) {
annotationUI.clearSelectedAnnotations();
annotationUI.selectTab(type);
......
......@@ -14,7 +14,7 @@ var fakeDocumentMeta = {
describe('sidebar.components.annotation-header', function () {
var $componentController;
var fakeGroups;
var fakeSettings;
var fakeSettings = { usernameUrl: 'http://www.example.org/' };
var fakeServiceUrl;
before(function () {
......@@ -93,5 +93,31 @@ describe('sidebar.components.annotation-header', function () {
assert.deepEqual(ctrl.displayName(), 'Bill Jones');
});
});
describe('#thirdPartyUsernameLink', () => {
it('returns the custom username link if set', () => {
var ann;
var ctrl;
fakeSettings.usernameUrl = 'http://www.example.org/';
ann = fixtures.defaultAnnotation();
ctrl = $componentController('annotationHeader', {}, {
annotation: ann,
});
assert.deepEqual(ctrl.thirdPartyUsernameLink(), 'http://www.example.org/bill');
});
it('returns null if no custom username link is set in the settings object', () => {
var ann;
var ctrl;
fakeSettings.usernameUrl = null;
ann = fixtures.defaultAnnotation();
ctrl = $componentController('annotationHeader', {}, {
annotation: ann,
});
assert.deepEqual(ctrl.thirdPartyUsernameLink(), null);
});
});
});
});
'use strict';
var angular = require('angular');
var events = require('../../events');
var util = require('../../directive/test/util');
describe('newNoteBtn', function () {
var $rootScope;
var sandbox = sinon.sandbox.create();
var fakeAnnotationUI = {
frames: sinon.stub().returns([{ id: null, uri: 'www.example.org'}, { id: '1', uri: 'www.example.org'}]),
};
before(function () {
angular.module('app', [])
.component('selectionTabs', require('../selection-tabs'))
.component('newNoteBtn', require('../new-note-btn'));
});
beforeEach(function () {
var fakeFeatures = {
flagEnabled: sinon.stub().returns(true),
};
var fakeSettings = { theme: 'clean' };
angular.mock.module('app', {
annotationUI: fakeAnnotationUI,
features: fakeFeatures,
settings: fakeSettings,
});
angular.mock.inject(function (_$componentController_, _$rootScope_) {
$rootScope = _$rootScope_;
});
});
afterEach(function() {
sandbox.restore();
});
it('should broadcast BEFORE_ANNOTATION_CREATED event when the new note button is clicked', function () {
var annot = {
target: [],
uri: 'www.example.org',
};
var elem = util.createDirective(document, 'newNoteBtn', {
annotationUI: fakeAnnotationUI,
});
sandbox.spy($rootScope, '$broadcast');
elem.ctrl.onNewNoteBtnClick();
assert.calledWith($rootScope.$broadcast, events.BEFORE_ANNOTATION_CREATED, annot);
});
});
......@@ -15,10 +15,12 @@ describe('selectionTabs', function () {
var fakeFeatures = {
flagEnabled: sinon.stub().returns(true),
};
var fakeSettings = {};
angular.mock.module('app', {
annotationUI: fakeAnnotationUI,
features: fakeFeatures,
settings: fakeSettings,
});
});
......@@ -59,5 +61,52 @@ describe('selectionTabs', function () {
var tabs = elem[0].querySelectorAll('a');
assert.isTrue(tabs[1].classList.contains('is-selected'));
});
it('should not show the clean theme when settings does not contain the clean theme option', function () {
var elem = util.createDirective(document, 'selectionTabs', {
selectedTab: 'annotation',
totalAnnotations: '123',
totalNotes: '456',
});
assert.isFalse(elem[0].querySelectorAll('.selection-tabs')[0].classList.contains('selection-tabs--theme-clean'));
});
it('should show the clean theme when settings contains the clean theme option', function () {
angular.mock.module('app', {
annotationUI: {},
features: {
flagEnabled: sinon.stub().returns(true),
},
settings: { theme: 'clean'},
});
var elem = util.createDirective(document, 'selectionTabs', {
selectedTab: 'annotation',
totalAnnotations: '123',
totalNotes: '456',
});
assert.isTrue(elem[0].querySelectorAll('.selection-tabs')[0].classList.contains('selection-tabs--theme-clean'));
});
it('should display the new note button when the notes tab is active', function () {
var elem = util.createDirective(document, 'selectionTabs', {
selectedTab: 'note',
totalAnnotations: '123',
totalNotes: '456',
});
var newNoteElem = elem[0].querySelectorAll('new-note-btn');
assert.equal(newNoteElem.length, 1);
});
it('should not display the new new note button when the annotations tab is active', function () {
var elem = util.createDirective(document, 'selectionTabs', {
selectedTab: 'annotation',
totalAnnotations: '123',
totalNotes: '456',
});
var newNoteElem = elem[0].querySelectorAll('new-note-btn');
assert.equal(newNoteElem.length, 0);
});
});
});
......@@ -41,6 +41,7 @@ var threadFixtures = immutable({
});
var fakeVirtualThread;
var fakeSettings = {};
function FakeVirtualThreadList($scope, $window, rootThread, options) {
......@@ -119,6 +120,7 @@ describe('threadList', function () {
beforeEach(function () {
angular.mock.module('app', {
VirtualThreadList: FakeVirtualThreadList,
settings: fakeSettings,
});
threadListContainers = [];
});
......@@ -129,6 +131,17 @@ describe('threadList', function () {
});
});
it('shows the clean theme when settings contains the clean theme option', function () {
angular.mock.module('app', {
VirtualThreadList: FakeVirtualThreadList,
settings: { theme: 'clean'},
});
var element = createThreadList();
fakeVirtualThread.notify();
element.scope.$digest();
assert.equal(element[0].querySelectorAll('.thread-list__card--theme-clean').length, element[0].querySelectorAll('annotation-thread').length);
});
it('displays the children of the root thread', function () {
var element = createThreadList();
fakeVirtualThread.notify();
......
......@@ -5,6 +5,8 @@ var angular = require('angular');
var util = require('../../directive/test/util');
describe('topBar', function () {
var fakeSettings = {};
before(function () {
angular.module('app', [])
.component('topBar', require('../top-bar'))
......@@ -20,7 +22,9 @@ describe('topBar', function () {
});
beforeEach(function () {
angular.mock.module('app');
angular.mock.module('app', {
settings: fakeSettings,
});
});
function applyUpdateBtn(el) {
......@@ -125,4 +129,13 @@ describe('topBar', function () {
sortDropdown.onChangeSortKey({sortKey: 'Oldest'});
assert.calledWith(onChangeSortKey, 'Oldest');
});
it('shows the clean theme when settings contains the clean theme option', function () {
angular.mock.module('app', {
settings: { theme: 'clean' },
});
var el = createTopBar();
assert.ok(el[0].querySelector('.top-bar--theme-clean'));
});
});
......@@ -49,7 +49,7 @@ var virtualThreadOptions = {
};
// @ngInject
function ThreadListController($element, $scope, VirtualThreadList) {
function ThreadListController($element, $scope, settings, VirtualThreadList) {
// `visibleThreads` keeps track of the subset of all threads matching the
// current filters which are in or near the viewport and the view then renders
// only those threads, using placeholders above and below the visible threads
......@@ -65,6 +65,8 @@ function ThreadListController($element, $scope, VirtualThreadList) {
// Firefox. See https://github.com/hypothesis/client/issues/341
this.scrollRoot = document.querySelector('.js-thread-list-scroll-root');
this.isThemeClean = settings.theme === 'clean';
var options = Object.assign({
scrollRoot: this.scrollRoot,
}, virtualThreadOptions);
......
......@@ -2,6 +2,14 @@
module.exports = {
controllerAs: 'vm',
//@ngInject
controller: function (settings) {
if (settings.theme && settings.theme === 'clean') {
this.isThemeClean = true;
} else {
this.isThemeClean = false;
}
},
bindings: {
auth: '<',
isSidebar: '<',
......
......@@ -35,6 +35,12 @@ function hostPageConfig(window) {
// OAuth feature flag override.
// This should be removed once OAuth is enabled for first party accounts.
'oauthEnabled',
// Theme which can either be specified as 'clean'.
// If nothing is the specified the classic look is applied.
'theme',
'usernameUrl',
];
return Object.keys(config).reduce(function (result, key) {
......
......@@ -6,8 +6,13 @@
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()"
ng-if="vm.isThirdPartyUser() && !vm.thirdPartyUsernameLink()"
>{{vm.displayName()}}</span>
<span class="annotation-collapsed-replies">
<a class="annotation-link" href=""
......
<button class="new-note__create" ng-click="vm.onNewNoteBtnClick()" h-branding="ctaBackgroundColor">
+ New note
</button>
<!-- Tabbed display of annotations and notes. -->
<div class="selection-tabs">
<div class="selection-tabs"
ng-class="{'selection-tabs--theme-clean' : vm.isThemeClean }">
<a class="selection-tabs__type"
href="#"
ng-class="{'is-selected': vm.selectedTab === vm.TAB_ANNOTATIONS}"
......@@ -32,6 +33,9 @@
</span>
</a>
</div>
<new-note-btn
ng-if="vm.selectedTab === vm.TAB_NOTES">
</new-note-btn>
<div ng-if="!vm.isLoading()" class="selection-tabs__empty-message">
<div ng-if="vm.showNotesUnavailableMessage()" class="annotation-unavailable-message">
<p class="annotation-unavailable-message__label">
......
......@@ -37,7 +37,6 @@
</span>
</p>
</div>
<thread-list
on-change-collapsed="vm.setCollapsed(id, collapsed)"
on-clear-selection="vm.clearSelection()"
......
<ul class="thread-list">
<li class="thread-list__spacer"
ng-style="{height: vm.virtualThreadList.offscreenUpperHeight}"></li>
<li id="{{child.id}}"
class="thread-list__card"
ng-mouseenter="vm.onFocus({annotation: child.annotation})"
ng-click="vm.onSelect({annotation: child.annotation})"
ng-mouseleave="vm.onFocus({annotation: null})"
ng-repeat="child in vm.virtualThreadList.visibleThreads track by child.id">
<annotation-thread
thread="child"
show-document-info="vm.showDocumentInfo"
on-change-collapsed="vm.onChangeCollapsed({id: id, collapsed: collapsed})"
on-force-visible="vm.onForceVisible({thread: thread})">
</annotation-thread>
<li ng-repeat="child in vm.virtualThreadList.visibleThreads track by child.id">
<div id="{{child.id}}"
class="thread-list__card"
ng-mouseenter="vm.onFocus({annotation: child.annotation})"
ng-class="{'thread-list__card--theme-clean' : vm.isThemeClean }"
ng-click="vm.onSelect({annotation: child.annotation})"
ng-mouseleave="vm.onFocus({annotation: null})">
<annotation-thread
thread="child"
show-document-info="vm.showDocumentInfo"
on-change-collapsed="vm.onChangeCollapsed({id: id, collapsed: collapsed})"
on-force-visible="vm.onForceVisible({thread: thread})">
</annotation-thread>
</div>
<hr ng-if="vm.isThemeClean"
class="thread-list__separator--theme-clean" />
</li>
<li id="{{child.id}}"
ng-show="false"
......
<!-- top bar for the sidebar and the stream.
!-->
<div class="top-bar">
<div class="top-bar"
ng-class="{'top-bar--theme-clean' : vm.isThemeClean }">
<!-- Legacy design for top bar, as used in the stream !-->
<div class="top-bar__inner content" ng-if="::!vm.isSidebar">
<search-input
......
......@@ -148,6 +148,20 @@ $base-font-size: 14px;
// to match the height of the top bar
height: 40px;
}
.annotator-frame-button--sidebar_close {
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.5);
border-radius: 0px;
border-style: solid none solid solid;
width: 27px;
margin-top: 140px;
margin-left: 6px;
height: 27px;
}
}
.annotator-frame--drop-shadow-enabled {
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.5);
}
.annotator-placeholder {
......
......@@ -19,6 +19,7 @@ $base-line-height: 20px;
@import './login-control';
@import './markdown';
@import './moderation-banner';
@import './new-note';
@import './primary-action-btn';
@import './publish-annotation-btn';
@import './search-status-bar';
......
.new-note__create {
background-color: $color-dove-gray;
border: none;
border-radius: 3px;
color: #fff;
display: flex;
font-weight: 500;
margin-left: auto;
margin-right: 14px;
margin-bottom: 10px;
text-align: center;
}
......@@ -12,6 +12,10 @@
padding-bottom: 10px;
}
.selection-tabs--theme-clean {
margin-left: 15px;
}
.selection-tabs__type {
color: $grey-6;
margin-right: 20px;
......
......@@ -17,9 +17,23 @@
}
}
.thread-list__card--theme-clean {
box-shadow: none;
&:hover {
box-shadow: none;
}
}
.thread-list__spacer {
// This is a hidden element which is used to reserve space for off-screen
// threads, so it should not occupy any space other than that set via its
// 'height' inline style property.
margin: 0;
}
.thread-list__separator--theme-clean {
border: 0;
border-top: 1px solid #E1E1E1;
margin: -8px 15px 10px 15px;
}
......@@ -17,6 +17,10 @@
transform: translate3d(0,0,0);
}
.top-bar--theme-clean {
border-bottom: none;
}
.top-bar__inner {
// the edges of the top-bar's contents should be aligned
// with the edges of annotation cards displayed below
......
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