diff --git a/__tests__/fixtures/version-3/001.json b/__tests__/fixtures/version-3/001.json index c43fab55e4447c29897c408508cc3d244d548e20..a148c32bc80fa32dd057210a5d33649e818d3ba5 100644 --- a/__tests__/fixtures/version-3/001.json +++ b/__tests__/fixtures/version-3/001.json @@ -286,11 +286,18 @@ "target": "https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json#xywh=2000,500,2000,2000" }, { - "body": { - "language": "en", - "type": "TextualBody", - "value": "this is a face" - }, + "body": [ + { + "language": "en", + "type": "TextualBody", + "value": "this is a face" + }, + { + "type": "TextualBody", + "value": "Face", + "purpose": "tagging" + } + ], "id": "https://example.org/iiif/book1/page/manifest/9c6934ee-1026-4a10-8a97-aaf513513020", "motivation": "commenting", "target": { @@ -301,6 +308,30 @@ } }, "type": "Annotation" + }, + { + "id": "https://example.org/iiif/book1/page/manifest/a3", + "type": "Annotation", + "motivation": "tagging", + "body": [ + { + "type": "TextualBody", + "value": "Tree" + }, + { + "type": "TextualBody", + "value": "Drawing" + }, + { + "type": "TextualBody", + "value": "Coniferous" + }, + { + "type": "TextualBody", + "value": "Stanford Cardinal" + } + ], + "target": "https://iiif.bodleian.ox.ac.uk/iiif/canvas/9cca8fdd-4a61-4429-8ac1-f648764b4d6d.json#xywh=1605,961,437,625" } ] }, diff --git a/__tests__/src/components/CanvasAnnotations.test.js b/__tests__/src/components/CanvasAnnotations.test.js index c8f8c318a279ae4aecacd8d06f03d2958541eb88..a3d0e5d7dff0bf696319971a298d56c95a7eb2be 100644 --- a/__tests__/src/components/CanvasAnnotations.test.js +++ b/__tests__/src/components/CanvasAnnotations.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import Typography from '@material-ui/core/Typography'; +import Chip from '@material-ui/core/Chip'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import { CanvasAnnotations } from '../../../src/components/CanvasAnnotations'; @@ -30,11 +31,13 @@ describe('CanvasAnnotations', () => { { content: 'First Annotation', id: 'abc123', + tags: ['abc123', 'def456'], targetId: 'example.com/iiif/12345', }, { content: 'Last Annotation', id: 'xyz321', + tags: [], targetId: 'example.com/iiif/54321', }, ]; @@ -61,6 +64,12 @@ describe('CanvasAnnotations', () => { expect(wrapper.find(ListItem).length).toEqual(2); }); + it('renders a Chip for every tag', () => { + wrapper = createWrapper({ annotations }); + + expect(wrapper.find(Chip).length).toEqual(2); + }); + it('pass through the annotation to make plugins life easier', () => { wrapper = createWrapper({ annotations }); expect(wrapper.find(ListItem).first().dive().props().annotation.id).toEqual('abc123'); @@ -107,6 +116,7 @@ describe('CanvasAnnotations', () => { { content: 'Annotation', id: 'annoId', + tags: [], targetId: 'example.com/iiif/12345', }, ], @@ -130,6 +140,7 @@ describe('CanvasAnnotations', () => { { content: 'Annotation', id: 'annoId', + tags: [], targetId: 'example.com/iiif/12345', }, ], @@ -148,6 +159,7 @@ describe('CanvasAnnotations', () => { { content: 'Annotation', id: 'annoId', + tags: [], targetId: 'example.com/iiif/12345', }, ], @@ -166,6 +178,7 @@ describe('CanvasAnnotations', () => { { content: 'Annotation', id: 'annoId', + tags: [], targetId: 'example.com/iiif/12345', }, ], diff --git a/__tests__/src/lib/AnnotationItem.test.js b/__tests__/src/lib/AnnotationItem.test.js index 995d7a555ac6d6c96c06be8aabbeea55dece0978..bf8345e9f90b4da88324245651968c423a83f853 100644 --- a/__tests__/src/lib/AnnotationItem.test.js +++ b/__tests__/src/lib/AnnotationItem.test.js @@ -12,6 +12,33 @@ describe('AnnotationItem', () => { }); }); + describe('isOnlyTag', () => { + it('when the only motivation is tagging', () => { + expect(new AnnotationItem({ motivation: 'tagging' }).isOnlyTag()) + .toBe(true); + }); + it('when there are other motivations besides tagging', () => { + expect(new AnnotationItem({ motivation: ['commenting', 'tagging'] }).isOnlyTag()) + .toBe(false); + }); + }); + + describe('tags', () => { + it('when only motivation', () => { + expect( + new AnnotationItem({ body: [{ purpose: 'tagging', value: 'yo' }, { purpose: 'tagging', value: 'lo' }] }).tags, + ).toEqual(['yo', 'lo']); + }); + it('when multiple motivations', () => { + expect( + new AnnotationItem({ + body: [{ purpose: 'commenting', value: 'yo' }, { purpose: 'tagging', value: 'lo' }], + motivation: ['commenting', 'tagging'], + }).tags, + ).toEqual(['lo']); + }); + }); + describe('targetId', () => { it('removes fragmentSelector coords from string targets', () => { expect( diff --git a/__tests__/src/lib/AnnotationResource.test.js b/__tests__/src/lib/AnnotationResource.test.js index 08dc0a0d79b9eced4cc3fc0ddb3b7493c896e3d6..9fd5cb941e728a65d19e6ffa5d8a79d71da3de7f 100644 --- a/__tests__/src/lib/AnnotationResource.test.js +++ b/__tests__/src/lib/AnnotationResource.test.js @@ -12,6 +12,33 @@ describe('AnnotationResource', () => { }); }); + describe('isOnlyTag', () => { + it('when the only motivation is tagging', () => { + expect(new AnnotationResource({ motivation: 'oa:tagging' }).isOnlyTag()) + .toBe(true); + }); + it('when there are other motivations besides tagging', () => { + expect(new AnnotationResource({ motivation: ['oa:commenting', 'oa:tagging'] }).isOnlyTag()) + .toBe(false); + }); + }); + + describe('tags', () => { + it('when only motivation', () => { + expect( + new AnnotationResource({ resource: [{ '@type': 'oa:Tag', value: 'yo' }, { '@type': 'oa:Tag', value: 'lo' }] }).tags, + ).toEqual(['yo', 'lo']); + }); + it('when multiple motivations', () => { + expect( + new AnnotationResource({ + motivation: ['oa:commenting', 'oa:tagging'], + resource: [{ '@type': 'oa:commenting', value: 'yo' }, { '@type': 'oa:Tag', value: 'lo' }], + }).tags, + ).toEqual(['lo']); + }); + }); + describe('targetId', () => { it('removes fragmentSelector coords from string targets', () => { expect( diff --git a/src/components/CanvasAnnotations.js b/src/components/CanvasAnnotations.js index 5e3af239184645c5d288d077471d26ffd02d25d7..a55e4f52ab10d5b6718ca516d0d65597be0aaf25 100644 --- a/src/components/CanvasAnnotations.js +++ b/src/components/CanvasAnnotations.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import Chip from '@material-ui/core/Chip'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; @@ -89,6 +90,13 @@ export class CanvasAnnotations extends Component { ruleSet={htmlSanitizationRuleSet} htmlString={annotation.content} /> + <div> + { + annotation.tags.map(tag => ( + <Chip size="small" variant="outlined" label={tag} id={tag} className={classes.chip} /> + )) + } + </div> </ListItemText> </ListItem> )) diff --git a/src/config/settings.js b/src/config/settings.js index 66890bebb6a31ca3d767958bce8426b521922ff3..b423cebd5590bd806c506e5ed0ad630c7fac15d3 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -216,7 +216,7 @@ export default { }, annotations: { htmlSanitizationRuleSet: 'iiif', // See src/lib/htmlRules.js for acceptable values - filteredMotivations: ['oa:commenting', 'sc:painting', 'commenting'], + filteredMotivations: ['oa:commenting', 'oa:tagging', 'sc:painting', 'commenting', 'tagging'], }, classPrefix: 'mirador', displayAllAnnotations: false, // Configure if annotations to be displayed on the canvas by default when fetched diff --git a/src/containers/CanvasAnnotations.js b/src/containers/CanvasAnnotations.js index 67d5a803f10a3da707c20dccee448681d2019e2f..cab58bd7f91a3a6ee4d66069070e641e079d770f 100644 --- a/src/containers/CanvasAnnotations.js +++ b/src/containers/CanvasAnnotations.js @@ -20,6 +20,7 @@ function getIdAndContentOfResources(resources) { return resources.map((resource, i) => ({ content: resource.chars, id: resource.id, + tags: resource.tags, targetId: resource.targetId, })); } @@ -60,6 +61,11 @@ const styles = theme => ({ borderBottom: `0.5px solid ${theme.palette.divider}`, cursor: 'pointer', }, + chip: { + backgroundColor: theme.palette.background.paper, + marginRight: theme.spacing(0.5), + marginTop: theme.spacing(1), + }, sectionHeading: { paddingLeft: theme.spacing(2), paddingRight: theme.spacing(1), diff --git a/src/lib/AnnotationItem.js b/src/lib/AnnotationItem.js index 10d59223aae462852397b8313acc46dea759cd4e..02f69f13e3a4b1f1cf34212f4016f09a241ab4e3 100644 --- a/src/lib/AnnotationItem.js +++ b/src/lib/AnnotationItem.js @@ -11,6 +11,11 @@ export default class AnnotationItem { this.resource = resource; } + /** */ + isOnlyTag() { + return (this.motivations.length === 1 && this.motivations[0] === 'tagging'); + } + /** */ get id() { this._id = this._id || this.resource.id || uuid(); // eslint-disable-line no-underscore-dangle @@ -47,6 +52,14 @@ export default class AnnotationItem { return this.body; } + /** */ + get tags() { + if (this.isOnlyTag()) { + return this.body.map(r => r.value); + } + return this.body.filter(r => r.purpose === 'tagging').map(r => r.value); + } + /** */ get target() { return flatten(compact(new Array(this.resource.target))); @@ -54,7 +67,8 @@ export default class AnnotationItem { /** */ get chars() { - return this.body.map(r => r.value).join(' '); + if (this.isOnlyTag()) return null; + return this.body.filter(r => r.purpose !== 'tagging').map(r => r.value).join(' '); } /** */ diff --git a/src/lib/AnnotationResource.js b/src/lib/AnnotationResource.js index 1ec5bf0244dfa899b2e0bedae33853676e592011..2a9437521895c24fcec1a745adb0308210d794b6 100644 --- a/src/lib/AnnotationResource.js +++ b/src/lib/AnnotationResource.js @@ -9,6 +9,11 @@ export default class AnnotationResource { this.resource = resource; } + /** */ + isOnlyTag() { + return (this.motivations.length === 1 && this.motivations[0] === 'oa:tagging'); + } + /** */ get id() { this._id = this._id || this.resource['@id'] || uuid(); // eslint-disable-line no-underscore-dangle @@ -45,9 +50,17 @@ export default class AnnotationResource { return flatten(compact(new Array(this.resource.on))); } + /** */ + get tags() { + if (this.isOnlyTag()) { + return this.resources.map(r => r.value); + } + return this.resources.filter(r => r['@type'] === 'oa:Tag').map(r => r.value); + } + /** */ get chars() { - return this.resources.map(r => r.chars).join(' '); + return this.resources.filter(r => r['@type'] !== 'oa:Tag').map(r => r.chars).join(' '); } /** */