Commit 1635f714 authored by Robert Knight's avatar Robert Knight

Convert Guest class to TypeScript syntax

parent 445bfef7
...@@ -25,37 +25,33 @@ import { findClosestOffscreenAnchor } from './util/buckets'; ...@@ -25,37 +25,33 @@ import { findClosestOffscreenAnchor } from './util/buckets';
import { frameFillsAncestor } from './util/frame'; import { frameFillsAncestor } from './util/frame';
import { normalizeURI } from './util/url'; import { normalizeURI } from './util/url';
/** import type {
* @typedef {import('../types/annotator').AnnotationData} AnnotationData AnnotationData,
* @typedef {import('../types/annotator').Annotator} Annotator Annotator,
* @typedef {import('../types/annotator').Anchor} Anchor Anchor,
* @typedef {import('../types/annotator').ContentInfoConfig} ContentInfoConfig ContentInfoConfig,
* @typedef {import('../types/annotator').Destroyable} Destroyable Destroyable,
* @typedef {import('../types/annotator').SidebarLayout} SidebarLayout Integration,
* @typedef {import('../types/api').Target} Target SidebarLayout,
* @typedef {import('../types/port-rpc-events').HostToGuestEvent} HostToGuestEvent } from '../types/annotator';
* @typedef {import('../types/port-rpc-events').GuestToHostEvent} GuestToHostEvent import type { Target } from '../types/api';
* @typedef {import('../types/port-rpc-events').GuestToSidebarEvent} GuestToSidebarEvent import type {
* @typedef {import('../types/port-rpc-events').SidebarToGuestEvent} SidebarToGuestEvent HostToGuestEvent,
*/ GuestToHostEvent,
GuestToSidebarEvent,
/** SidebarToGuestEvent,
* HTML element created by the highlighter with an associated annotation. } from '../types/port-rpc-events';
*
* @typedef {HTMLElement & { _annotation?: AnnotationData }} AnnotationHighlight /** HTML element created by the highlighter with an associated annotation. */
*/ type AnnotationHighlight = HTMLElement & { _annotation?: AnnotationData };
/** /** Return all the annotations tags associated with the selected text. */
* Return all the annotations tags associated with the selected text. function annotationsForSelection(): string[] {
* const selection = window.getSelection()!;
* @return {string[]}
*/
function annotationsForSelection() {
const selection = /** @type {Selection} */ (window.getSelection());
const range = selection.getRangeAt(0); const range = selection.getRangeAt(0);
const tags = rangeUtil.itemsForRange( const tags = rangeUtil.itemsForRange(
range, range,
node => /** @type {AnnotationHighlight} */ (node)._annotation?.$tag node => (node as AnnotationHighlight)._annotation?.$tag
); );
return tags; return tags;
} }
...@@ -63,16 +59,13 @@ function annotationsForSelection() { ...@@ -63,16 +59,13 @@ function annotationsForSelection() {
/** /**
* Return the annotation tags associated with any highlights that contain a given * Return the annotation tags associated with any highlights that contain a given
* DOM node. * DOM node.
*
* @param {Node} node
* @return {string[]}
*/ */
function annotationsAt(node) { function annotationsAt(node: Node): string[] {
const items = getHighlightsContainingNode(node) const items = getHighlightsContainingNode(node)
.map(h => /** @type {AnnotationHighlight} */ (h)._annotation) .map(h => (h as AnnotationHighlight)._annotation)
.filter(ann => ann !== undefined) .filter(ann => ann !== undefined)
.map(ann => ann?.$tag); .map(ann => ann?.$tag);
return /** @type {string[]} */ (items); return items as string[];
} }
/** /**
...@@ -80,11 +73,8 @@ function annotationsAt(node) { ...@@ -80,11 +73,8 @@ function annotationsAt(node) {
* *
* This may fail if anchoring failed or if the document has been mutated since * This may fail if anchoring failed or if the document has been mutated since
* the anchor was created in a way that invalidates the anchor. * the anchor was created in a way that invalidates the anchor.
*
* @param {Anchor} anchor
* @return {Range|null}
*/ */
function resolveAnchor(anchor) { function resolveAnchor(anchor: Anchor): Range | null {
if (!anchor.range) { if (!anchor.range) {
return null; return null;
} }
...@@ -101,14 +91,17 @@ function removeTextSelection() { ...@@ -101,14 +91,17 @@ function removeTextSelection() {
/** /**
* Subset of the Hypothesis client configuration that is used by {@link Guest}. * Subset of the Hypothesis client configuration that is used by {@link Guest}.
*
* @typedef GuestConfig
* @prop {string} [subFrameIdentifier] - An identifier used by this guest to
* identify the current frame when communicating with the sidebar. This is
* only set in non-host frames.
* @prop {ContentInfoConfig} [contentInfoBanner] - Configures a banner or other indicators
* showing where the content has come from.
*/ */
export type GuestConfig = {
/**
* An identifier used by this guest to identify the current frame when
* communicating with the sidebar. This is only set in non-host frames.
*/
subFrameIdentifier?: string;
/** Configures a banner or other indicators showing where the content has come from. */
contentInfoBanner?: ContentInfoConfig;
};
/** /**
* `Guest` is the central class of the annotator that handles anchoring (locating) * `Guest` is the central class of the annotator that handles anchoring (locating)
...@@ -126,27 +119,83 @@ function removeTextSelection() { ...@@ -126,27 +119,83 @@ function removeTextSelection() {
* class that shows the sidebar app and surrounding UI. The `Guest` instance in * class that shows the sidebar app and surrounding UI. The `Guest` instance in
* each frame connects to the sidebar and host frames as part of its * each frame connects to the sidebar and host frames as part of its
* initialization. * initialization.
*
* @implements {Annotator}
* @implements {Destroyable}
*/ */
export class Guest { export class Guest implements Annotator, Destroyable {
public element: HTMLElement;
/** Ranges of the current text selection. */
public selectedRanges: Range[];
/** /**
* @param {HTMLElement} element - * The anchors generated by resolving annotation selectors to locations in the
* document. These are added by `anchor` and removed by `detach`.
*
* There is one anchor per annotation `Target`, which typically means one
* anchor per annotation.
*/
public anchors: Anchor[];
public features: FeatureFlags;
private _adder: Adder;
private _clusterToolbar?: HighlightClusterController;
private _hostFrame: Window;
private _highlightsVisible: boolean;
private _isAdderVisible: boolean;
private _informHostOnNextSelectionClear: boolean;
private _selectionObserver: SelectionObserver;
/**
* Tags of annotations that are currently anchored or being anchored in
* the guest.
*/
private _annotations: Set<string>;
private _frameIdentifier: string | null;
private _portFinder: PortFinder;
/**
* Integration that handles document-type specific functionality in the
* guest.
*/
private _integration: Integration;
/** Channel for host-guest communication. */
private _hostRPC: PortRPC<HostToGuestEvent, GuestToHostEvent>;
/** Channel for guest-sidebar communication. */
private _sidebarRPC: PortRPC<SidebarToGuestEvent, GuestToSidebarEvent>;
private _bucketBarClient: BucketBarClient;
private _sideBySideActive: boolean;
private _listeners: ListenerCollection;
/**
* Tags of currently hovered annotations. This is used to set the hovered
* state correctly for new highlights if the associated annotation is already
* hovered in the sidebar.
*/
private _hoveredAnnotations: Set<string>;
/**
* @param element -
* The root element in which the `Guest` instance should be able to anchor * The root element in which the `Guest` instance should be able to anchor
* or create annotations. In an ordinary web page this typically `document.body`. * or create annotations. In an ordinary web page this typically `document.body`.
* @param {GuestConfig} [config] * @param [config]
* @param {Window} [hostFrame] - * @param [hostFrame] -
* Host frame which this guest is associated with. This is expected to be * Host frame which this guest is associated with. This is expected to be
* an ancestor of the guest frame. It may be same or cross origin. * an ancestor of the guest frame. It may be same or cross origin.
*/ */
constructor(element, config = {}, hostFrame = window) { constructor(
element: HTMLElement,
config: GuestConfig = {},
hostFrame: Window = window
) {
this.element = element; this.element = element;
this._hostFrame = hostFrame; this._hostFrame = hostFrame;
this._highlightsVisible = false; this._highlightsVisible = false;
this._isAdderVisible = false; this._isAdderVisible = false;
this._informHostOnNextSelectionClear = true; this._informHostOnNextSelectionClear = true;
/** @type {Range[]} - Ranges of the current text selection. */
this.selectedRanges = []; this.selectedRanges = [];
this._adder = new Adder(this.element, { this._adder = new Adder(this.element, {
...@@ -170,26 +219,11 @@ export class Guest { ...@@ -170,26 +219,11 @@ export class Guest {
} }
}); });
/**
* The anchors generated by resolving annotation selectors to locations in the
* document. These are added by `anchor` and removed by `detach`.
*
* There is one anchor per annotation `Target`, which typically means one
* anchor per annotation.
*
* @type {Anchor[]}
*/
this.anchors = []; this.anchors = [];
this._annotations = new Set();
/**
* Tags of annotations that are currently anchored or being anchored in
* the guest.
*/
this._annotations = /** @type {Set<string>} */ (new Set());
// Set the frame identifier if it's available. // Set the frame identifier if it's available.
// The "top" guest instance will have this as null since it's in a top frame not a sub frame // The "top" guest instance will have this as null since it's in a top frame not a sub frame
/** @type {string|null} */
this._frameIdentifier = config.subFrameIdentifier || null; this._frameIdentifier = config.subFrameIdentifier || null;
this._portFinder = new PortFinder({ this._portFinder = new PortFinder({
...@@ -199,10 +233,7 @@ export class Guest { ...@@ -199,10 +233,7 @@ export class Guest {
}); });
this.features = new FeatureFlags(); this.features = new FeatureFlags();
/**
* Integration that handles document-type specific functionality in the
* guest.
*/
this._integration = createIntegration(this); this._integration = createIntegration(this);
this._integration.on('uriChanged', async () => { this._integration.on('uriChanged', async () => {
const metadata = await this.getDocumentInfo(); const metadata = await this.getDocumentInfo();
...@@ -218,19 +249,9 @@ export class Guest { ...@@ -218,19 +249,9 @@ export class Guest {
}); });
} }
/**
* Channel for host-guest communication.
*
* @type {PortRPC<HostToGuestEvent, GuestToHostEvent>}
*/
this._hostRPC = new PortRPC(); this._hostRPC = new PortRPC();
this._connectHost(hostFrame); this._connectHost(hostFrame);
/**
* Channel for guest-sidebar communication.
*
* @type {PortRPC<SidebarToGuestEvent, GuestToSidebarEvent>}
*/
this._sidebarRPC = new PortRPC(); this._sidebarRPC = new PortRPC();
this._connectSidebar(); this._connectSidebar();
...@@ -245,13 +266,6 @@ export class Guest { ...@@ -245,13 +266,6 @@ export class Guest {
this._listeners = new ListenerCollection(); this._listeners = new ListenerCollection();
this._setupElementEvents(); this._setupElementEvents();
/**
* Tags of currently hovered annotations. This is used to set the hovered
* state correctly for new highlights if the associated annotation is already
* hovered in the sidebar.
*
* @type {Set<string>}
*/
this._hoveredAnnotations = new Set(); this._hoveredAnnotations = new Set();
} }
...@@ -260,8 +274,7 @@ export class Guest { ...@@ -260,8 +274,7 @@ export class Guest {
_setupElementEvents() { _setupElementEvents() {
// Hide the sidebar in response to a document click or tap, so it doesn't obscure // Hide the sidebar in response to a document click or tap, so it doesn't obscure
// the document content. // the document content.
/** @param {Element} element */ const maybeCloseSidebar = (element: Element) => {
const maybeCloseSidebar = element => {
if (this._sideBySideActive) { if (this._sideBySideActive) {
// Don't hide the sidebar if event was disabled because the sidebar // Don't hide the sidebar if event was disabled because the sidebar
// doesn't overlap the content. // doesn't overlap the content.
...@@ -276,7 +289,7 @@ export class Guest { ...@@ -276,7 +289,7 @@ export class Guest {
this._listeners.add(this.element, 'mouseup', event => { this._listeners.add(this.element, 'mouseup', event => {
const { target, metaKey, ctrlKey } = event; const { target, metaKey, ctrlKey } = event;
const tags = annotationsAt(/** @type {Element} */ (target)); const tags = annotationsAt(target as Element);
if (tags.length && this._highlightsVisible) { if (tags.length && this._highlightsVisible) {
const toggle = metaKey || ctrlKey; const toggle = metaKey || ctrlKey;
this.selectAnnotations(tags, { toggle }); this.selectAnnotations(tags, { toggle });
...@@ -284,17 +297,17 @@ export class Guest { ...@@ -284,17 +297,17 @@ export class Guest {
}); });
this._listeners.add(this.element, 'mousedown', ({ target }) => { this._listeners.add(this.element, 'mousedown', ({ target }) => {
maybeCloseSidebar(/** @type {Element} */ (target)); maybeCloseSidebar(target as Element);
}); });
// Allow taps on the document to hide the sidebar as well as clicks. // Allow taps on the document to hide the sidebar as well as clicks.
// On iOS < 13 (2019), elements like h2 or div don't emit 'click' events. // On iOS < 13 (2019), elements like h2 or div don't emit 'click' events.
this._listeners.add(this.element, 'touchstart', ({ target }) => { this._listeners.add(this.element, 'touchstart', ({ target }) => {
maybeCloseSidebar(/** @type {Element} */ (target)); maybeCloseSidebar(target as Element);
}); });
this._listeners.add(this.element, 'mouseover', ({ target }) => { this._listeners.add(this.element, 'mouseover', ({ target }) => {
const tags = annotationsAt(/** @type {Element} */ (target)); const tags = annotationsAt(target as Element);
if (tags.length && this._highlightsVisible) { if (tags.length && this._highlightsVisible) {
this._sidebarRPC.call('hoverAnnotations', tags); this._sidebarRPC.call('hoverAnnotations', tags);
} }
...@@ -343,8 +356,7 @@ export class Guest { ...@@ -343,8 +356,7 @@ export class Guest {
} }
} }
/** @param {Window} hostFrame */ async _connectHost(hostFrame: Window) {
async _connectHost(hostFrame) {
this._hostRPC.on('clearSelection', () => { this._hostRPC.on('clearSelection', () => {
if (selectedRange(document)) { if (selectedRange(document)) {
this._informHostOnNextSelectionClear = false; this._informHostOnNextSelectionClear = false;
...@@ -354,39 +366,25 @@ export class Guest { ...@@ -354,39 +366,25 @@ export class Guest {
this._hostRPC.on('createAnnotation', () => this.createAnnotation()); this._hostRPC.on('createAnnotation', () => this.createAnnotation());
this._hostRPC.on( this._hostRPC.on('hoverAnnotations', (tags: string[]) =>
'hoverAnnotations', this._hoverAnnotations(tags)
/** @param {string[]} tags */
tags => this._hoverAnnotations(tags)
); );
this._hostRPC.on( this._hostRPC.on(
'scrollToClosestOffScreenAnchor', 'scrollToClosestOffScreenAnchor',
/** (tags: string[], direction: 'down' | 'up') =>
* @param {string[]} tags this._scrollToClosestOffScreenAnchor(tags, direction)
* @param {'down'|'up'} direction
*/
(tags, direction) => this._scrollToClosestOffScreenAnchor(tags, direction)
); );
this._hostRPC.on( this._hostRPC.on('selectAnnotations', (tags: string[], toggle: boolean) =>
'selectAnnotations', this.selectAnnotations(tags, { toggle })
/**
* @param {string[]} tags
* @param {boolean} toggle
*/
(tags, toggle) => this.selectAnnotations(tags, { toggle })
); );
this._hostRPC.on( this._hostRPC.on('sidebarLayoutChanged', (sidebarLayout: SidebarLayout) => {
'sidebarLayoutChanged', if (frameFillsAncestor(window, hostFrame)) {
/** @param {SidebarLayout} sidebarLayout */ this.fitSideBySide(sidebarLayout);
sidebarLayout => {
if (frameFillsAncestor(window, hostFrame)) {
this.fitSideBySide(sidebarLayout);
}
} }
); });
// Discover and connect to the host frame. All RPC events must be // Discover and connect to the host frame. All RPC events must be
// registered before creating the channel. // registered before creating the channel.
...@@ -397,71 +395,53 @@ export class Guest { ...@@ -397,71 +395,53 @@ export class Guest {
async _connectSidebar() { async _connectSidebar() {
this._sidebarRPC.on( this._sidebarRPC.on(
'featureFlagsUpdated', 'featureFlagsUpdated',
/** @param {Record<string, boolean>} flags */ flags => (flags: Record<string, boolean>) => this.features.update(flags)
this.features.update(flags)
); );
// Handlers for events sent when user hovers or clicks on an annotation card // Handlers for events sent when user hovers or clicks on an annotation card
// in the sidebar. // in the sidebar.
this._sidebarRPC.on( this._sidebarRPC.on('hoverAnnotations', (tags: string[]) =>
'hoverAnnotations', this._hoverAnnotations(tags)
/** @param {string[]} tags */
tags => this._hoverAnnotations(tags)
); );
this._sidebarRPC.on( this._sidebarRPC.on('scrollToAnnotation', (tag: string) => {
'scrollToAnnotation', const anchor = this.anchors.find(a => a.annotation.$tag === tag);
/** @param {string} tag */ if (!anchor?.highlights) {
tag => { return;
const anchor = this.anchors.find(a => a.annotation.$tag === tag); }
if (!anchor?.highlights) { const range = resolveAnchor(anchor);
return; if (!range) {
} return;
const range = resolveAnchor(anchor);
if (!range) {
return;
}
// Emit a custom event that the host page can respond to. This is useful,
// for example, if the highlighted content is contained in a collapsible
// section of the page that needs to be un-collapsed.
const event = new CustomEvent('scrolltorange', {
bubbles: true,
cancelable: true,
detail: range,
});
const defaultNotPrevented = this.element.dispatchEvent(event);
if (defaultNotPrevented) {
this._integration.scrollToAnchor(anchor);
}
} }
);
// Handler for controls on the sidebar // Emit a custom event that the host page can respond to. This is useful,
this._sidebarRPC.on( // for example, if the highlighted content is contained in a collapsible
'setHighlightsVisible', // section of the page that needs to be un-collapsed.
/** @param {boolean} showHighlights */ showHighlights => { const event = new CustomEvent('scrolltorange', {
this.setHighlightsVisible(showHighlights, false /* notifyHost */); bubbles: true,
cancelable: true,
detail: range,
});
const defaultNotPrevented = this.element.dispatchEvent(event);
if (defaultNotPrevented) {
this._integration.scrollToAnchor(anchor);
} }
); });
this._sidebarRPC.on( // Handler for controls on the sidebar
'deleteAnnotation', this._sidebarRPC.on('setHighlightsVisible', (showHighlights: boolean) => {
/** @param {string} tag */ this.setHighlightsVisible(showHighlights, false /* notifyHost */);
tag => this.detach(tag) });
);
this._sidebarRPC.on( this._sidebarRPC.on('deleteAnnotation', (tag: string) => this.detach(tag));
'loadAnnotations',
/** @param {AnnotationData[]} annotations */ this._sidebarRPC.on('loadAnnotations', (annotations: AnnotationData[]) =>
annotations => annotations.forEach(annotation => this.anchor(annotation)) annotations.forEach(annotation => this.anchor(annotation))
); );
this._sidebarRPC.on( this._sidebarRPC.on('showContentInfo', (info: ContentInfoConfig) =>
'showContentInfo', this._integration.showContentInfo?.(info)
/** @param {ContentInfoConfig} info */
info => this._integration.showContentInfo?.(info)
); );
// Connect to sidebar and send document info/URIs to it. // Connect to sidebar and send document info/URIs to it.
...@@ -501,18 +481,12 @@ export class Guest { ...@@ -501,18 +481,12 @@ export class Guest {
* *
* Any existing anchors associated with `annotation` will be removed before * Any existing anchors associated with `annotation` will be removed before
* re-anchoring the annotation. * re-anchoring the annotation.
*
* @param {AnnotationData} annotation
* @return {Promise<Anchor[]>}
*/ */
async anchor(annotation) { async anchor(annotation: AnnotationData): Promise<Anchor[]> {
/** /**
* Resolve an annotation's selectors to a concrete range. * Resolve an annotation's selectors to a concrete range.
*
* @param {Target} target
* @return {Promise<Anchor>}
*/ */
const locate = async target => { const locate = async (target: Target): Promise<Anchor> => {
// Only annotations with an associated quote can currently be anchored. // Only annotations with an associated quote can currently be anchored.
// This is because the quote is used to verify anchoring with other selector // This is because the quote is used to verify anchoring with other selector
// types. // types.
...@@ -523,8 +497,7 @@ export class Guest { ...@@ -523,8 +497,7 @@ export class Guest {
return { annotation, target }; return { annotation, target };
} }
/** @type {Anchor} */ let anchor: Anchor;
let anchor;
try { try {
const range = await this._integration.anchor( const range = await this._integration.anchor(
this.element, this.element,
...@@ -544,21 +517,17 @@ export class Guest { ...@@ -544,21 +517,17 @@ export class Guest {
/** /**
* Highlight the text range that `anchor` refers to. * Highlight the text range that `anchor` refers to.
*
* @param {Anchor} anchor
*/ */
const highlight = anchor => { const highlight = (anchor: Anchor) => {
const range = resolveAnchor(anchor); const range = resolveAnchor(anchor);
if (!range) { if (!range) {
return; return;
} }
const highlights = /** @type {AnnotationHighlight[]} */ ( const highlights = highlightRange(
highlightRange( range,
range, classnames('hypothesis-highlight', anchor.annotation?.$cluster)
classnames('hypothesis-highlight', anchor.annotation?.$cluster) ) as AnnotationHighlight[];
)
);
highlights.forEach(h => { highlights.forEach(h => {
h._annotation = anchor.annotation; h._annotation = anchor.annotation;
}); });
...@@ -585,7 +554,7 @@ export class Guest { ...@@ -585,7 +554,7 @@ export class Guest {
return []; return [];
} }
for (let anchor of anchors) { for (const anchor of anchors) {
highlight(anchor); highlight(anchor);
} }
...@@ -607,16 +576,14 @@ export class Guest { ...@@ -607,16 +576,14 @@ export class Guest {
/** /**
* Remove the anchors and associated highlights for an annotation from the document. * Remove the anchors and associated highlights for an annotation from the document.
* *
* @param {string} tag * @param [notify] - For internal use. Whether to inform the host
* @param {boolean} [notify] - For internal use. Whether to inform the host
* frame about the removal of an anchor. * frame about the removal of an anchor.
*/ */
detach(tag, notify = true) { detach(tag: string, notify = true) {
this._annotations.delete(tag); this._annotations.delete(tag);
/** @type {Anchor[]} */ const anchors = [] as Anchor[];
const anchors = []; for (const anchor of this.anchors) {
for (let anchor of this.anchors) {
if (anchor.annotation.$tag !== tag) { if (anchor.annotation.$tag !== tag) {
anchors.push(anchor); anchors.push(anchor);
} else if (anchor.highlights) { } else if (anchor.highlights) {
...@@ -626,11 +593,7 @@ export class Guest { ...@@ -626,11 +593,7 @@ export class Guest {
this._updateAnchors(anchors, notify); this._updateAnchors(anchors, notify);
} }
/** _updateAnchors(anchors: Anchor[], notify: boolean) {
* @param {Anchor[]} anchors
* @param {boolean} notify
*/
_updateAnchors(anchors, notify) {
this.anchors = anchors; this.anchors = anchors;
if (notify) { if (notify) {
this._bucketBarClient.update(this.anchors); this._bucketBarClient.update(this.anchors);
...@@ -641,13 +604,13 @@ export class Guest { ...@@ -641,13 +604,13 @@ export class Guest {
* Create a new annotation that is associated with the selected region of * Create a new annotation that is associated with the selected region of
* the current document. * the current document.
* *
* @param {object} options * @param options
* @param {boolean} [options.highlight] - If true, the new annotation has * @param [options.highlight] - If true, the new annotation has
* the `$highlight` flag set, causing it to be saved immediately without * the `$highlight` flag set, causing it to be saved immediately without
* prompting for a comment. * prompting for a comment.
* @return {Promise<AnnotationData>} - The new annotation * @return The new annotation
*/ */
async createAnnotation({ highlight = false } = {}) { async createAnnotation({ highlight = false } = {}): Promise<AnnotationData> {
const ranges = this.selectedRanges; const ranges = this.selectedRanges;
this.selectedRanges = []; this.selectedRanges = [];
...@@ -664,8 +627,7 @@ export class Guest { ...@@ -664,8 +627,7 @@ export class Guest {
selector: selectors, selector: selectors,
})); }));
/** @type {AnnotationData} */ const annotation: AnnotationData = {
const annotation = {
uri: info.uri, uri: info.uri,
document: info.metadata, document: info.metadata,
target, target,
...@@ -687,14 +649,12 @@ export class Guest { ...@@ -687,14 +649,12 @@ export class Guest {
/** /**
* Indicate in the sidebar that certain annotations are focused (ie. the * Indicate in the sidebar that certain annotations are focused (ie. the
* associated document region(s) is hovered). * associated document region(s) is hovered).
*
* @param {string[]} tags
*/ */
_hoverAnnotations(tags) { _hoverAnnotations(tags: string[]) {
this._hoveredAnnotations.clear(); this._hoveredAnnotations.clear();
tags.forEach(tag => this._hoveredAnnotations.add(tag)); tags.forEach(tag => this._hoveredAnnotations.add(tag));
for (let anchor of this.anchors) { for (const anchor of this.anchors) {
if (anchor.highlights) { if (anchor.highlights) {
const toggle = tags.includes(anchor.annotation.$tag); const toggle = tags.includes(anchor.annotation.$tag);
setHighlightsFocused(anchor.highlights, toggle); setHighlightsFocused(anchor.highlights, toggle);
...@@ -706,11 +666,8 @@ export class Guest { ...@@ -706,11 +666,8 @@ export class Guest {
/** /**
* Scroll to the closest off screen anchor. * Scroll to the closest off screen anchor.
*
* @param {string[]} tags
* @param {'down'|'up'} direction
*/ */
_scrollToClosestOffScreenAnchor(tags, direction) { _scrollToClosestOffScreenAnchor(tags: string[], direction: 'down' | 'up') {
const anchors = this.anchors.filter(({ annotation }) => const anchors = this.anchors.filter(({ annotation }) =>
tags.includes(annotation.$tag) tags.includes(annotation.$tag)
); );
...@@ -722,16 +679,14 @@ export class Guest { ...@@ -722,16 +679,14 @@ export class Guest {
/** /**
* Show or hide the adder toolbar when the selection changes. * Show or hide the adder toolbar when the selection changes.
*
* @param {Range} range
*/ */
_onSelection(range) { _onSelection(range: Range) {
if (!this._integration.canAnnotate(range)) { if (!this._integration.canAnnotate(range)) {
this._onClearSelection(); this._onClearSelection();
return; return;
} }
const selection = /** @type {Selection} */ (document.getSelection()); const selection = document.getSelection()!;
const isBackwards = rangeUtil.isSelectionBackwards(selection); const isBackwards = rangeUtil.isSelectionBackwards(selection);
const focusRect = rangeUtil.selectionFocusRect(selection); const focusRect = rangeUtil.selectionFocusRect(selection);
if (!focusRect) { if (!focusRect) {
...@@ -765,15 +720,18 @@ export class Guest { ...@@ -765,15 +720,18 @@ export class Guest {
* and opens the sidebar. Optionally it can also transfer keyboard focus to * and opens the sidebar. Optionally it can also transfer keyboard focus to
* the annotation card for the first selected annotation. * the annotation card for the first selected annotation.
* *
* @param {string[]} tags * @param tags
* @param {object} options * @param options
* @param {boolean} [options.toggle] - Toggle whether the annotations are * @param [options.toggle] - Toggle whether the annotations are
* selected, as opposed to just selecting them * selected, as opposed to just selecting them
* @param {boolean} [options.focusInSidebar] - Whether to transfer keyboard * @param [options.focusInSidebar] - Whether to transfer keyboard
* focus to the card for the first annotation in the selection. This * focus to the card for the first annotation in the selection. This
* option has no effect if {@link toggle} is true. * option has no effect if {@link toggle} is true.
*/ */
selectAnnotations(tags, { toggle = false, focusInSidebar = false } = {}) { selectAnnotations(
tags: string[],
{ toggle = false, focusInSidebar = false } = {}
) {
if (toggle) { if (toggle) {
this._sidebarRPC.call('toggleAnnotationSelection', tags); this._sidebarRPC.call('toggleAnnotationSelection', tags);
} else { } else {
...@@ -785,12 +743,12 @@ export class Guest { ...@@ -785,12 +743,12 @@ export class Guest {
/** /**
* Set whether highlights are visible in the document or not. * Set whether highlights are visible in the document or not.
* *
* @param {boolean} visible * @param visible
* @param {boolean} notifyHost - Whether to notify the host frame about this * @param notifyHost - Whether to notify the host frame about this
* change. This should be true unless the request to change highlight * change. This should be true unless the request to change highlight
* visibility is coming from the host frame. * visibility is coming from the host frame.
*/ */
setHighlightsVisible(visible, notifyHost = true) { setHighlightsVisible(visible: boolean, notifyHost = true) {
setHighlightsVisible(this.element, visible); setHighlightsVisible(this.element, visible);
this._highlightsVisible = visible; this._highlightsVisible = visible;
if (notifyHost) { if (notifyHost) {
...@@ -805,9 +763,9 @@ export class Guest { ...@@ -805,9 +763,9 @@ export class Guest {
/** /**
* Attempt to fit the document content alongside the sidebar. * Attempt to fit the document content alongside the sidebar.
* *
* @param {SidebarLayout} sidebarLayout * @param sidebarLayout
*/ */
fitSideBySide(sidebarLayout) { fitSideBySide(sidebarLayout: SidebarLayout) {
this._sideBySideActive = this._integration.fitSideBySide(sidebarLayout); this._sideBySideActive = this._integration.fitSideBySide(sidebarLayout);
} }
...@@ -825,19 +783,15 @@ export class Guest { ...@@ -825,19 +783,15 @@ export class Guest {
/** /**
* Return the tags of annotations that are currently displayed in a hovered * Return the tags of annotations that are currently displayed in a hovered
* state. * state.
*
* @return {Set<string>}
*/ */
get hoveredAnnotationTags() { get hoveredAnnotationTags(): Set<string> {
return this._hoveredAnnotations; return this._hoveredAnnotations;
} }
/** /**
* Handle a potential shortcut trigger. * Handle a potential shortcut trigger.
*
* @param {KeyboardEvent} event
*/ */
_handleShortcut(event) { _handleShortcut(event: KeyboardEvent) {
if (matchShortcut(event, 'Ctrl+Shift+H')) { if (matchShortcut(event, 'Ctrl+Shift+H')) {
this.setHighlightsVisible(!this._highlightsVisible); this.setHighlightsVisible(!this._highlightsVisible);
} }
......
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