Commit 5fc7d62e authored by Robert Knight's avatar Robert Knight

Convert HTMLIntegration to TypeScript

Aside from syntax changes, the `anchor` and `describe` properties were converted
to regular methods instead of fields assigned in the constructor, as that is
more consistent with other integrations and easier for TS to grok.
parent 812b271f
import { TinyEmitter } from 'tiny-emitter'; import { TinyEmitter } from 'tiny-emitter';
import type {
Anchor,
FeatureFlags,
Integration,
SidebarLayout,
} from '../../types/annotator';
import type { Selector } from '../../types/api';
import { anchor, describe } from '../anchoring/html'; import { anchor, describe } from '../anchoring/html';
import { TextRange } from '../anchoring/text-range'; import { TextRange } from '../anchoring/text-range';
import { NavigationObserver } from '../util/navigation-observer'; import { NavigationObserver } from '../util/navigation-observer';
...@@ -10,14 +17,6 @@ import { ...@@ -10,14 +17,6 @@ import {
preserveScrollPosition, preserveScrollPosition,
} from './html-side-by-side'; } from './html-side-by-side';
/**
* @typedef {import('../../types/annotator').Anchor} Anchor
* @typedef {import('../../types/annotator').Annotator} Annotator
* @typedef {import('../../types/annotator').FeatureFlags} FeatureFlags
* @typedef {import('../../types/annotator').Integration} Integration
* @typedef {import('../../types/annotator').SidebarLayout} SidebarLayout
*/
// When activating side-by-side mode, make sure there is at least this amount // When activating side-by-side mode, make sure there is at least this amount
// of space (in pixels) left for the document's content. Any narrower and the // of space (in pixels) left for the document's content. Any narrower and the
// content line lengths and scale are too short to be readable. // content line lengths and scale are too short to be readable.
...@@ -28,36 +27,46 @@ const MIN_HTML_WIDTH = 480; ...@@ -28,36 +27,46 @@ const MIN_HTML_WIDTH = 480;
* *
* This integration is used for web pages and applications that are not handled * This integration is used for web pages and applications that are not handled
* by a more specific integration (eg. for PDFs). * by a more specific integration (eg. for PDFs).
*
* @implements {Integration}
*/ */
export class HTMLIntegration extends TinyEmitter { export class HTMLIntegration extends TinyEmitter implements Integration {
container: HTMLElement;
features: FeatureFlags;
private _flagsChanged: () => void;
private _htmlMeta: HTMLMetadata;
private _prevURI: string;
/** Whether to attempt to resize the document to fit alongside sidebar. */
private _sideBySideEnabled: boolean;
/** /**
* @param {object} options * Whether the document is currently being resized to fit alongside an
* @param {FeatureFlags} options.features * open sidebar.
* @param {HTMLElement} [options.container]
*/ */
constructor({ features, container = document.body }) { private _sideBySideActive: boolean;
private _lastLayout: SidebarLayout | null;
private _navObserver: NavigationObserver;
private _metaObserver: MutationObserver;
constructor({
features,
container = document.body,
}: {
features: FeatureFlags;
container?: HTMLElement;
}) {
super(); super();
this.features = features; this.features = features;
this.container = container; this.container = container;
this.anchor = anchor;
this.describe = describe;
this._htmlMeta = new HTMLMetadata(); this._htmlMeta = new HTMLMetadata();
this._prevURI = this._htmlMeta.uri(); this._prevURI = this._htmlMeta.uri();
/** Whether to attempt to resize the document to fit alongside sidebar. */
this._sideBySideEnabled = this.features.flagEnabled('html_side_by_side'); this._sideBySideEnabled = this.features.flagEnabled('html_side_by_side');
/**
* Whether the document is currently being resized to fit alongside an
* open sidebar.
*/
this._sideBySideActive = false; this._sideBySideActive = false;
/** @type {SidebarLayout|null} */
this._lastLayout = null; this._lastLayout = null;
// Watch for changes to `location.href`. // Watch for changes to `location.href`.
...@@ -97,6 +106,14 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -97,6 +106,14 @@ export class HTMLIntegration extends TinyEmitter {
this.features.on('flagsChanged', this._flagsChanged); this.features.on('flagsChanged', this._flagsChanged);
} }
anchor(root: Element, selectors: Selector[]): Promise<Range> {
return anchor(root, selectors);
}
describe(root: Element, range: Range): Selector[] {
return describe(root, range);
}
_checkForURIChange() { _checkForURIChange() {
const currentURI = this._htmlMeta.uri(); const currentURI = this._htmlMeta.uri();
if (currentURI !== this._prevURI) { if (currentURI !== this._prevURI) {
...@@ -108,10 +125,8 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -108,10 +125,8 @@ export class HTMLIntegration extends TinyEmitter {
/** /**
* Return a Range trimmed to remove any leading or trailing whitespace, or * Return a Range trimmed to remove any leading or trailing whitespace, or
* `null` if no valid trimmed Range can be created from `range` * `null` if no valid trimmed Range can be created from `range`
*
* @param {Range} range
*/ */
getAnnotatableRange(range) { getAnnotatableRange(range: Range) {
try { try {
return TextRange.trimmedRange(range); return TextRange.trimmedRange(range);
} catch (err) { } catch (err) {
...@@ -136,10 +151,7 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -136,10 +151,7 @@ export class HTMLIntegration extends TinyEmitter {
return this.container; return this.container;
} }
/** fitSideBySide(layout: SidebarLayout) {
* @param {SidebarLayout} layout
*/
fitSideBySide(layout) {
this._lastLayout = layout; this._lastLayout = layout;
const maximumWidthToFit = window.innerWidth - layout.width; const maximumWidthToFit = window.innerWidth - layout.width;
...@@ -161,10 +173,8 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -161,10 +173,8 @@ export class HTMLIntegration extends TinyEmitter {
/** /**
* Resize the document content after side-by-side mode is activated. * Resize the document content after side-by-side mode is activated.
*
* @param {number} sidebarWidth
*/ */
_activateSideBySide(sidebarWidth) { _activateSideBySide(sidebarWidth: number) {
// When side-by-side mode is activated, what we want to achieve is that the // When side-by-side mode is activated, what we want to achieve is that the
// main content of the page is fully visible alongside the sidebar, with // main content of the page is fully visible alongside the sidebar, with
// as much space given to the main content as possible. A challenge is that // as much space given to the main content as possible. A challenge is that
...@@ -201,8 +211,7 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -201,8 +211,7 @@ export class HTMLIntegration extends TinyEmitter {
const padding = 12; const padding = 12;
const rightMargin = sidebarWidth + padding; const rightMargin = sidebarWidth + padding;
/** @param {HTMLElement} element */ const computeLeftMargin = (element: HTMLElement) =>
const computeLeftMargin = element =>
parseInt(window.getComputedStyle(element).marginLeft, 10); parseInt(window.getComputedStyle(element).marginLeft, 10);
preserveScrollPosition(() => { preserveScrollPosition(() => {
...@@ -270,10 +279,7 @@ export class HTMLIntegration extends TinyEmitter { ...@@ -270,10 +279,7 @@ export class HTMLIntegration extends TinyEmitter {
return this._htmlMeta.uri(); return this._htmlMeta.uri();
} }
/** async scrollToAnchor(anchor: Anchor) {
* @param {Anchor} anchor
*/
async scrollToAnchor(anchor) {
const highlight = anchor.highlights?.[0]; const highlight = anchor.highlights?.[0];
if (!highlight) { if (!highlight) {
return; return;
......
...@@ -68,10 +68,16 @@ describe('HTMLIntegration', () => { ...@@ -68,10 +68,16 @@ describe('HTMLIntegration', () => {
return new HTMLIntegration({ features }); return new HTMLIntegration({ features });
} }
it('implements `anchor` and `destroy` using HTML anchoring', () => { it('implements `anchor` and `destroy` using HTML anchoring', async () => {
const integration = createIntegration(); const integration = createIntegration();
assert.equal(integration.anchor, fakeHTMLAnchoring.anchor); const root = {};
assert.equal(integration.describe, fakeHTMLAnchoring.describe); const selectors = [];
const range = await integration.anchor(root, selectors);
assert.calledWith(fakeHTMLAnchoring.anchor, root, selectors);
integration.describe(root, range);
assert.calledWith(fakeHTMLAnchoring.describe, root, range);
}); });
describe('#getAnnotatableRange', () => { describe('#getAnnotatableRange', () => {
......
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