Skip to content

Commit d641c24

Browse files
committed
Include text from shape selector in thumbnail alt text
Include extracted document text from shape selectors in the alt text of thumbnails.
1 parent 6bcb4d5 commit d641c24

File tree

6 files changed

+60
-11
lines changed

6 files changed

+60
-11
lines changed

src/sidebar/components/Annotation/Annotation.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,12 @@ function Annotation({
124124
replyCount={replyCount}
125125
threadIsCollapsed={threadIsCollapsed}
126126
/>
127-
{targetShape && <AnnotationThumbnail tag={annotation.$tag} />}
127+
{targetShape && (
128+
<AnnotationThumbnail
129+
tag={annotation.$tag}
130+
textInImage={targetShape.text}
131+
/>
132+
)}
128133
{annotationQuote && (
129134
<AnnotationQuote
130135
quote={annotationQuote}

src/sidebar/components/Annotation/AnnotationThumbnail.tsx

+23-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ function BitmapImage({ alt, bitmap, classes, scale = 1.0 }: BitmapImageProps) {
3131
width={bitmap.width}
3232
height={bitmap.height}
3333
role="img"
34+
// The `alt` attribute on an `<img>` maps to aria-label. We might want to
35+
// split this into a separate concise label and longer description in
36+
// future.
3437
aria-label={alt}
38+
// Set the title attribute to make it easy to inspect the alt text on
39+
// desktop. Screen readers will only read `aria-label` since it has the
40+
// same value.
41+
title={alt}
3542
className={classes}
3643
style={{
3744
width: `${bitmap.width / scale}px`,
@@ -44,9 +51,17 @@ function BitmapImage({ alt, bitmap, classes, scale = 1.0 }: BitmapImageProps) {
4451
export type AnnotationThumbnailProps = {
4552
tag: string;
4653
thumbnailService: ThumbnailService;
54+
55+
/**
56+
* Text contained in the thumbnail.
57+
*
58+
* This is used when generating alt text for the thumbnail.
59+
*/
60+
textInImage?: string;
4761
};
4862

4963
function AnnotationThumbnail({
64+
textInImage,
5065
tag,
5166
thumbnailService,
5267
}: AnnotationThumbnailProps) {
@@ -69,14 +84,21 @@ function AnnotationThumbnail({
6984
}
7085
}, [error, devicePixelRatio, tag, thumbnail, thumbnailService]);
7186

87+
let altText;
88+
if (textInImage) {
89+
altText = `Thumbnail. Contains text: ${textInImage}`;
90+
} else {
91+
altText = 'Thumbnail';
92+
}
93+
7294
return (
7395
<div
7496
className="flex flex-row justify-center"
7597
data-testid="thumbnail-container"
7698
>
7799
{thumbnail && (
78100
<BitmapImage
79-
alt="annotated content thumbnail"
101+
alt={altText}
80102
bitmap={thumbnail}
81103
classes="border rounded-md"
82104
scale={devicePixelRatio}

src/sidebar/components/Annotation/test/Annotation-test.js

+2
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,14 @@ describe('Annotation', () => {
157157
right: 10,
158158
bottom: 0,
159159
},
160+
text: 'Some text',
160161
},
161162
];
162163
const wrapper = createComponent({ annotation });
163164
const thumbnail = wrapper.find('AnnotationThumbnail');
164165
assert.isTrue(thumbnail.exists());
165166
assert.equal(thumbnail.prop('tag'), annotation.$tag);
167+
assert.equal(thumbnail.prop('textInImage'), 'Some text');
166168
});
167169
});
168170

src/sidebar/components/Annotation/test/AnnotationThumbnail-test.js

+18
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ describe('AnnotationThumbnail', () => {
3838
assert.isTrue(wrapper.exists('BitmapImage'));
3939
});
4040

41+
[
42+
{
43+
textInImage: undefined,
44+
expectedAlt: 'Thumbnail',
45+
},
46+
{
47+
textInImage: 'Foo bar',
48+
expectedAlt: 'Thumbnail. Contains text: Foo bar',
49+
},
50+
].forEach(({ textInImage, expectedAlt }) => {
51+
it('sets alt text for thumbnail', () => {
52+
fakeThumbnailService.get.returns(fakeThumbnail);
53+
const wrapper = createComponent({ textInImage });
54+
const image = wrapper.find('canvas');
55+
assert.equal(image.prop('aria-label'), expectedAlt);
56+
});
57+
});
58+
4159
it('requests thumbnail and then renders it if not cached', async () => {
4260
const wrapper = createComponent();
4361
assert.calledOnce(fakeThumbnailService.fetch);

src/sidebar/helpers/annotation-metadata.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {
44
EPUBContentSelector,
55
PageSelector,
66
SavedAnnotation,
7-
Shape,
87
ShapeSelector,
98
TextQuoteSelector,
109
} from '../../types/api';
@@ -401,11 +400,11 @@ export function quote(annotation: APIAnnotationData): string | null {
401400
* This will return `null` if the annotation is associated with a text
402401
* selection instead of a shape.
403402
*/
404-
export function shape(annotation: APIAnnotationData): Shape | null {
403+
export function shape(annotation: APIAnnotationData): ShapeSelector | null {
405404
const shapeSelector = annotation.target[0]?.selector?.find(
406405
s => s.type === 'ShapeSelector',
407406
) as ShapeSelector | undefined;
408-
return shapeSelector?.shape ?? null;
407+
return shapeSelector ?? null;
409408
}
410409

411410
/**

src/sidebar/helpers/test/annotation-metadata-test.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -726,15 +726,18 @@ describe('sidebar/helpers/annotation-metadata', () => {
726726
},
727727
],
728728
expected: {
729-
type: 'rect',
730-
left: 0,
731-
top: 10,
732-
right: 10,
733-
bottom: 0,
729+
type: 'ShapeSelector',
730+
shape: {
731+
type: 'rect',
732+
left: 0,
733+
top: 10,
734+
right: 10,
735+
bottom: 0,
736+
},
734737
},
735738
},
736739
].forEach(({ selectors, expected }) => {
737-
it('returns shape from shape selector', () => {
740+
it('returns shape selector', () => {
738741
const annotation = {
739742
target: [
740743
{

0 commit comments

Comments
 (0)