Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
1635f714
Commit
1635f714
authored
Nov 01, 2022
by
Robert Knight
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Convert Guest class to TypeScript syntax
parent
445bfef7
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
194 additions
and
240 deletions
+194
-240
guest.ts
src/annotator/guest.ts
+194
-240
No files found.
src/annotator/guest.
j
s
→
src/annotator/guest.
t
s
View file @
1635f714
...
@@ -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.
*/
export
class
Guest
implements
Annotator
,
Destroyable
{
public
element
:
HTMLElement
;
/** Ranges of the current text selection. */
public
selectedRanges
:
Range
[];
/**
* The anchors generated by resolving annotation selectors to locations in the
* document. These are added by `anchor` and removed by `detach`.
*
*
* @implements {Annotator}
* There is one anchor per annotation `Target`, which typically means one
* @implements {Destroyable}
* anchor per annotation.
*/
*/
export
class
Guest
{
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
;
/**
/**
* @param {HTMLElement} element -
* 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'
,
/** @param {SidebarLayout} sidebarLayout */
sidebarLayout
=>
{
if
(
frameFillsAncestor
(
window
,
hostFrame
))
{
if
(
frameFillsAncestor
(
window
,
hostFrame
))
{
this
.
fitSideBySide
(
sidebarLayout
);
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,22 +395,16 @@ export class Guest {
...
@@ -397,22 +395,16 @@ 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'
,
/** @param {string} tag */
tag
=>
{
const
anchor
=
this
.
anchors
.
find
(
a
=>
a
.
annotation
.
$tag
===
tag
);
const
anchor
=
this
.
anchors
.
find
(
a
=>
a
.
annotation
.
$tag
===
tag
);
if
(
!
anchor
?.
highlights
)
{
if
(
!
anchor
?.
highlights
)
{
return
;
return
;
...
@@ -435,33 +427,21 @@ export class Guest {
...
@@ -435,33 +427,21 @@ export class Guest {
if
(
defaultNotPrevented
)
{
if
(
defaultNotPrevented
)
{
this
.
_integration
.
scrollToAnchor
(
anchor
);
this
.
_integration
.
scrollToAnchor
(
anchor
);
}
}
}
});
);
// Handler for controls on the sidebar
// Handler for controls on the sidebar
this
.
_sidebarRPC
.
on
(
this
.
_sidebarRPC
.
on
(
'setHighlightsVisible'
,
(
showHighlights
:
boolean
)
=>
{
'setHighlightsVisible'
,
/** @param {boolean} showHighlights */
showHighlights
=>
{
this
.
setHighlightsVisible
(
showHighlights
,
false
/* notifyHost */
);
this
.
setHighlightsVisible
(
showHighlights
,
false
/* notifyHost */
);
}
});
);
this
.
_sidebarRPC
.
on
(
this
.
_sidebarRPC
.
on
(
'deleteAnnotation'
,
(
tag
:
string
)
=>
this
.
detach
(
tag
));
'deleteAnnotation'
,
/** @param {string} tag */
tag
=>
this
.
detach
(
tag
)
);
this
.
_sidebarRPC
.
on
(
this
.
_sidebarRPC
.
on
(
'loadAnnotations'
,
(
annotations
:
AnnotationData
[])
=>
'loadAnnotations'
,
annotations
.
forEach
(
annotation
=>
this
.
anchor
(
annotation
))
/** @param {AnnotationData[]} annotations */
annotations
=>
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
(
le
t
anchor
of
anchors
)
{
for
(
cons
t
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
(
le
t
anchor
of
this
.
anchors
)
{
for
(
cons
t
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
);
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment