Commit df07f8e7 authored by Robert Knight's avatar Robert Knight

Use book or chapter URL in VS integration depending on "book_as_single_document" flag

Add the plumbing needed to make the VitalSource integration return either the
book or chapter URL depending on the value of the "book_as_single_document"
flag. Additionally handle changes in this flag after the integration is created
by notifying the guest about the URI change.

When the flag is enabled, the "book URL" is currently a placeholder that will
in future be replaced by a real value fetched from the `<mosaic-book>`
element in the container frame.
parent 499fa138
...@@ -11,7 +11,7 @@ import { warnOnce } from '../shared/warn-once'; ...@@ -11,7 +11,7 @@ import { warnOnce } from '../shared/warn-once';
* *
* @type {string[]} * @type {string[]}
*/ */
const annotatorFlags = ['html_side_by_side']; const annotatorFlags = ['book_as_single_document', 'html_side_by_side'];
/** /**
* An observable container of feature flags. * An observable container of feature flags.
......
...@@ -25,7 +25,9 @@ export function createIntegration(annotator) { ...@@ -25,7 +25,9 @@ export function createIntegration(annotator) {
const vsFrameRole = vitalSourceFrameRole(); const vsFrameRole = vitalSourceFrameRole();
if (vsFrameRole === 'content') { if (vsFrameRole === 'content') {
return new VitalSourceContentIntegration(); return new VitalSourceContentIntegration(document.body, {
features: annotator.features,
});
} }
return new HTMLIntegration({ features: annotator.features }); return new HTMLIntegration({ features: annotator.features });
......
import { delay, waitFor } from '../../../test-util/wait'; import { delay, waitFor } from '../../../test-util/wait';
import { FeatureFlags } from '../../features';
import { import {
VitalSourceInjector, VitalSourceInjector,
VitalSourceContentIntegration, VitalSourceContentIntegration,
...@@ -54,12 +55,14 @@ class FakeVitalSourceViewer { ...@@ -54,12 +55,14 @@ class FakeVitalSourceViewer {
} }
describe('annotator/integrations/vitalsource', () => { describe('annotator/integrations/vitalsource', () => {
let featureFlags;
let fakeViewer; let fakeViewer;
let FakeHTMLIntegration; let FakeHTMLIntegration;
let fakeHTMLIntegration; let fakeHTMLIntegration;
let fakeInjectClient; let fakeInjectClient;
beforeEach(() => { beforeEach(() => {
featureFlags = new FeatureFlags();
fakeViewer = new FakeVitalSourceViewer(); fakeViewer = new FakeVitalSourceViewer();
fakeHTMLIntegration = { fakeHTMLIntegration = {
...@@ -192,7 +195,9 @@ describe('annotator/integrations/vitalsource', () => { ...@@ -192,7 +195,9 @@ describe('annotator/integrations/vitalsource', () => {
let integrations; let integrations;
function createIntegration() { function createIntegration() {
const integration = new VitalSourceContentIntegration(); const integration = new VitalSourceContentIntegration(document.body, {
features: featureFlags,
});
integrations.push(integration); integrations.push(integration);
return integration; return integration;
} }
...@@ -279,7 +284,8 @@ describe('annotator/integrations/vitalsource', () => { ...@@ -279,7 +284,8 @@ describe('annotator/integrations/vitalsource', () => {
history.back(); history.back();
}); });
it('returns book URL excluding query string', async () => { context('when "book_as_single_document" flag is off', () => {
it('returns book chapter URL excluding query string', async () => {
const integration = createIntegration(); const integration = createIntegration();
const uri = await integration.uri(); const uri = await integration.uri();
const parsedURL = new URL(uri); const parsedURL = new URL(uri);
...@@ -292,6 +298,32 @@ describe('annotator/integrations/vitalsource', () => { ...@@ -292,6 +298,32 @@ describe('annotator/integrations/vitalsource', () => {
}); });
}); });
context('when "book_as_single_document" flag is on', () => {
it('returns book reader URL', async () => {
featureFlags.update({ book_as_single_document: true });
const integration = createIntegration();
const uri = await integration.uri();
assert.equal(
uri,
'https://bookshelf.vitalsource.com/reader/books/1234'
);
});
});
it('updates when "book_as_single_document" flag changes', async () => {
const uriChanged = sinon.stub();
const integration = createIntegration();
integration.on('uriChanged', uriChanged);
const uri1 = await integration.uri();
featureFlags.update({ book_as_single_document: true });
assert.calledOnce(uriChanged);
const uri2 = await integration.uri();
assert.notEqual(uri1, uri2);
});
});
context('in PDF documents', () => { context('in PDF documents', () => {
let FakeImageTextLayer; let FakeImageTextLayer;
let fakeImageTextLayer; let fakeImageTextLayer;
......
...@@ -8,7 +8,12 @@ import { preserveScrollPosition } from './html-side-by-side'; ...@@ -8,7 +8,12 @@ import { preserveScrollPosition } from './html-side-by-side';
import { ImageTextLayer } from './image-text-layer'; import { ImageTextLayer } from './image-text-layer';
import { injectClient } from '../hypothesis-injector'; import { injectClient } from '../hypothesis-injector';
import type { Anchor, Integration, SidebarLayout } from '../../types/annotator'; import type {
Anchor,
FeatureFlags as IFeatureFlags,
Integration,
SidebarLayout,
} from '../../types/annotator';
import type { Selector } from '../../types/api'; import type { Selector } from '../../types/api';
import type { InjectConfig } from '../hypothesis-injector'; import type { InjectConfig } from '../hypothesis-injector';
...@@ -201,21 +206,36 @@ export class VitalSourceContentIntegration ...@@ -201,21 +206,36 @@ export class VitalSourceContentIntegration
extends TinyEmitter extends TinyEmitter
implements Integration implements Integration
{ {
private _features: IFeatureFlags;
private _htmlIntegration: HTMLIntegration; private _htmlIntegration: HTMLIntegration;
private _listeners: ListenerCollection; private _listeners: ListenerCollection;
private _textLayer?: ImageTextLayer; private _textLayer?: ImageTextLayer;
constructor(container: HTMLElement = document.body) { constructor(
container: HTMLElement = document.body,
options: { features: IFeatureFlags }
) {
super(); super();
const features = new FeatureFlags(); this._features = options.features;
// If the book_as_single_document flag changed, this will change the
// document URI returned by this integration.
this._features.on('flagsChanged', () => {
this.emit('uriChanged');
});
const htmlFeatures = new FeatureFlags();
// Forcibly enable the side-by-side feature for VS books. This feature is // Forcibly enable the side-by-side feature for VS books. This feature is
// only behind a flag for regular web pages, which are typically more // only behind a flag for regular web pages, which are typically more
// complex and varied than EPUB books. // complex and varied than EPUB books.
features.update({ html_side_by_side: true }); htmlFeatures.update({ html_side_by_side: true });
this._htmlIntegration = new HTMLIntegration({ container, features }); this._htmlIntegration = new HTMLIntegration({
container,
features: htmlFeatures,
});
this._listeners = new ListenerCollection(); this._listeners = new ListenerCollection();
...@@ -339,6 +359,12 @@ export class VitalSourceContentIntegration ...@@ -339,6 +359,12 @@ export class VitalSourceContentIntegration
} }
async uri() { async uri() {
if (this._bookIsSingleDocument()) {
// Dummy book ID that will in future be replaced with a real value
// retrieved via the `<mosaic-book>` element's API.
const bookId = '1234';
return `https://bookshelf.vitalsource.com/reader/books/${bookId}`;
} else {
// An example of a typical URL for the chapter content in the Bookshelf reader is: // An example of a typical URL for the chapter content in the Bookshelf reader is:
// //
// https://jigsaw.vitalsource.com/books/9781848317703/epub/OPS/xhtml/chapter_001.html#cfi=/6/10%5B;vnd.vst.idref=chap001%5D!/4 // https://jigsaw.vitalsource.com/books/9781848317703/epub/OPS/xhtml/chapter_001.html#cfi=/6/10%5B;vnd.vst.idref=chap001%5D!/4
...@@ -355,8 +381,17 @@ export class VitalSourceContentIntegration ...@@ -355,8 +381,17 @@ export class VitalSourceContentIntegration
uri.search = ''; uri.search = '';
return uri.toString(); return uri.toString();
} }
}
async scrollToAnchor(anchor: Anchor) { async scrollToAnchor(anchor: Anchor) {
return this._htmlIntegration.scrollToAnchor(anchor); return this._htmlIntegration.scrollToAnchor(anchor);
} }
/**
* Return true if the feature flag to treat books as one document is enabled,
* as opposed to treating each chapter/segment/page as a separate document.
*/
_bookIsSingleDocument(): boolean {
return this._features.flagEnabled('book_as_single_document');
}
} }
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