Commit c93835eb authored by Sheetal Umesh Kumar's avatar Sheetal Umesh Kumar

Show annotation count on the page, so that users don't have to open to sidebar...

Show annotation count on the page, so that users don't have to open to sidebar to know how many annotations are in the page.

Fixes: https://github.com/hypothesis/product-backlog/issues/129
parent 4610489c
......@@ -36,7 +36,11 @@ function LiveReloadServer(port, appServer) {
<title>Hypothesis Client Test</title>
</head>
<body>
<pre style="margin: 75px;">${changelogText()}</pre>
<div data-hypothesis-trigger style="margin: 75px 0 0 75px;">
Number of annotations:
<span data-hypothesis-annotation-count>...</span>
</div>
<pre style="margin: 20px 75px 75px 75px;">${changelogText()}</pre>
<script>
var appHost = document.location.hostname;
......
'use strict';
var events = require('../shared/bridge-events');
var ANNOTATION_COUNT_ATTR = 'data-hypothesis-annotation-count';
/**
* Update the elements in the container element with the count data attribute
* with the new annotation count.
*
* @param {Element} rootEl - The DOM element which contains the elements that
* display annotation count.
*/
function annotationCounts(rootEl, crossframe) {
crossframe.on(events.PUBLIC_ANNOTATION_COUNT_CHANGED, updateAnnotationCountElems);
function updateAnnotationCountElems(newCount) {
var elems = rootEl.querySelectorAll('['+ANNOTATION_COUNT_ATTR+']');
Array.from(elems).forEach(function(elem) {
elem.textContent = newCount;
});
}
}
module.exports = annotationCounts;
......@@ -3,6 +3,7 @@ raf = require('raf')
Hammer = require('hammerjs')
Host = require('./host')
annotationCounts = require('./annotation-counts')
sidebarTrigger = require('./sidebar-trigger')
# Minimum width to which the frame can be resized.
......@@ -37,13 +38,16 @@ module.exports = class Sidebar extends Host
this._setupSidebarEvents()
_setupDocumentEvents: ->
sidebarTrigger(document, @show.bind(this))
sidebarTrigger(document.body, @show.bind(this))
@element.on 'click', (event) =>
if !@selectedTargets?.length
this.hide()
return this
_setupSidebarEvents: ->
annotationCounts(document.body, @crossframe)
@crossframe.on('show', this.show.bind(this))
@crossframe.on('hide', this.hide.bind(this))
......
'use strict';
var annotationCounts = require('../annotation-counts');
describe('annotationCounts', function () {
var countEl1;
var countEl2;
var CrossFrame;
var fakeCrossFrame;
var sandbox;
beforeEach(function () {
CrossFrame = null;
fakeCrossFrame = {};
sandbox = sinon.sandbox.create();
countEl1 = document.createElement('button');
countEl1.setAttribute('data-hypothesis-annotation-count');
document.body.appendChild(countEl1);
countEl2 = document.createElement('button');
countEl2.setAttribute('data-hypothesis-annotation-count');
document.body.appendChild(countEl2);
fakeCrossFrame.on = sandbox.stub().returns(fakeCrossFrame);
CrossFrame = sandbox.stub();
CrossFrame.returns(fakeCrossFrame);
});
afterEach(function () {
sandbox.restore();
countEl1.remove();
countEl2.remove();
});
describe('listen for "publicAnnotationCountChanged" event', function () {
var emitEvent = function () {
var crossFrameArgs;
var evt;
var fn;
var event = arguments[0];
var args = 2 <= arguments.length ? Array.prototype.slice.call(arguments, 1) : [];
crossFrameArgs = fakeCrossFrame.on.args;
for (var i = 0, len = crossFrameArgs.length; i < len; i++) {
evt = crossFrameArgs[i][0];
fn = crossFrameArgs[i][1];
if (event === evt) {
fn.apply(null, args);
}
}
};
it('displays the updated annotation count on the appropriate elements', function () {
var newCount = 10;
annotationCounts(document.body, fakeCrossFrame);
emitEvent('publicAnnotationCountChanged', newCount);
assert.equal(countEl1.textContent, newCount);
assert.equal(countEl2.textContent, newCount);
});
});
});
'use strict';
/**
* This module defines the set of global events that are dispatched
* across the bridge between the sidebar and annotator
*/
module.exports = {
/** The set of annotations was updated. */
PUBLIC_ANNOTATION_COUNT_CHANGED: 'publicAnnotationCountChanged',
};
......@@ -101,6 +101,24 @@ function isNew(annotation) {
return !annotation.id;
}
/** Return `true` if the given annotation is public, `false` otherwise. */
function isPublic(annotation) {
var isPublic = false;
if (!annotation.permissions) {
return isPublic;
}
annotation.permissions.read.forEach(function(perm) {
var readPermArr = perm.split(':');
if (readPermArr.length === 2 && readPermArr[0] === 'group') {
isPublic = true;
}
});
return isPublic;
}
/**
* Return `true` if `annotation` has a selector.
*
......@@ -169,6 +187,7 @@ module.exports = {
isNew: isNew,
isOrphan: isOrphan,
isPageNote: isPageNote,
isPublic: isPublic,
isReply: isReply,
isWaitingToAnchor: isWaitingToAnchor,
location: location,
......
'use strict';
var events = require('./events');
var bridgeEvents = require('../shared/bridge-events');
var metadata = require('./annotation-metadata');
var uiConstants = require('./ui-constants');
......@@ -50,6 +51,7 @@ function FrameSync($rootScope, $window, Discovery, annotationUI, bridge) {
function setupSyncToFrame() {
// List of loaded annotations in previous state
var prevAnnotations = [];
var prevPublicAnns = 0;
annotationUI.subscribe(function () {
var state = annotationUI.getState();
......@@ -57,6 +59,7 @@ function FrameSync($rootScope, $window, Discovery, annotationUI, bridge) {
return;
}
var publicAnns = 0;
var inSidebar = new Set();
var added = [];
......@@ -66,6 +69,10 @@ function FrameSync($rootScope, $window, Discovery, annotationUI, bridge) {
return;
}
if (metadata.isPublic(annot)) {
++publicAnns;
}
inSidebar.add(annot.$tag);
if (!inFrame.has(annot.$tag)) {
added.push(annot);
......@@ -89,6 +96,11 @@ function FrameSync($rootScope, $window, Discovery, annotationUI, bridge) {
bridge.call('deleteAnnotation', formatAnnot(annot));
inFrame.delete(annot.$tag);
});
if (publicAnns !== prevPublicAnns) {
bridge.call(bridgeEvents.PUBLIC_ANNOTATION_COUNT_CHANGED, publicAnns);
prevPublicAnns = publicAnns;
}
});
}
......
......@@ -16,6 +16,25 @@ function defaultAnnotation() {
};
}
/**
* Return a fake public annotation with the basic properties filled in.
*/
function publicAnnotation() {
return {
id: 'pubann',
document: {
title: 'A special document',
},
permissions: {
read:['group:__world__'],
},
target: [{source: 'source', 'selector': []}],
uri: 'http://example.com',
user: 'acct:bill@localhost',
updated: '2015-05-10T20:18:56.613388+00:00',
};
}
/** Return an annotation domain model object for a new annotation
* (newly-created client-side, not yet saved to the server).
*/
......@@ -120,6 +139,7 @@ function oldReply() {
module.exports = {
defaultAnnotation: defaultAnnotation,
publicAnnotation: publicAnnotation,
newAnnotation: newAnnotation,
newEmptyAnnotation: newEmptyAnnotation,
newHighlight: newHighlight,
......
......@@ -3,6 +3,8 @@
var annotationMetadata = require('../annotation-metadata');
var fixtures = require('./annotation-fixtures');
var unroll = require('../../shared/test/util').unroll;
var documentMetadata = annotationMetadata.documentMetadata;
var domainAndTitle = annotationMetadata.domainAndTitle;
......@@ -247,6 +249,26 @@ describe('annotation-metadata', function () {
});
});
describe('.isPublic', function () {
it('returns true if an annotation is shared within a group', function () {
assert.isTrue(annotationMetadata.isPublic(fixtures.publicAnnotation()));
});
unroll('returns false if an annotation is not publicly readable', function (testCase) {
var annotation = Object.assign(fixtures.defaultAnnotation(), {permissions: testCase});
assert.isFalse(annotationMetadata.isPublic(annotation));
}, [{
read:['acct:someemail@localhost'],
}, {
read:['something invalid'],
}]);
it('returns false if an annotation is missing permissions', function () {
var annotation = Object.assign(fixtures.defaultAnnotation());
assert.isFalse(annotationMetadata.isPublic(annotation));
});
});
describe('.isOrphan', function () {
it('returns true if an annotation failed to anchor', function () {
var annotation = Object.assign(fixtures.defaultAnnotation(), {$orphan: true});
......
......@@ -121,6 +121,13 @@ describe('FrameSync', function () {
});
});
context('when annotation count has changed', function () {
it('sends a "publicAnnotationCountChanged" message to the frame', function () {
fakeAnnotationUI.setState({annotations: [annotationFixtures.publicAnnotation()]});
assert.calledWithMatch(fakeBridge.call, 'publicAnnotationCountChanged', sinon.match(1));
});
});
context('when annotations are removed from the sidebar', function () {
it('sends a "deleteAnnotation" message to the frame', function () {
fakeAnnotationUI.setState({annotations: [fixtures.ann]});
......
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