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
38e4d947
Commit
38e4d947
authored
Jan 26, 2023
by
Alejandro Celaya
Committed by
Alejandro Celaya
Jan 26, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Migrate adder to TS
parent
d883b0b0
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
95 additions
and
117 deletions
+95
-117
adder.tsx
src/annotator/adder.tsx
+85
-102
adder-test.js
src/annotator/test/adder-test.js
+10
-15
No files found.
src/annotator/adder.
js
→
src/annotator/adder.
tsx
View file @
38e4d947
import
{
render
}
from
'preact'
;
import
AdderToolbar
from
'./components/AdderToolbar'
;
import
type
{
Command
}
from
'./components/AdderToolbar'
;
import
{
isTouchDevice
}
from
'../shared/user-agent'
;
import
{
createShadowRoot
}
from
'./util/shadow-root
'
;
import
type
{
Destroyable
}
from
'../types/annotator
'
;
/**
* @typedef {1} ArrowPointingDown
* Show the adder above the selection with an arrow pointing down at the
* selected text.
*/
export
const
ARROW_POINTING_DOWN
=
1
;
/**
* @typedef {2} ArrowPointingUp
* Show the adder above the selection with an arrow pointing up at the
* selected text.
*/
export
const
ARROW_POINTING_UP
=
2
;
import
{
createShadowRoot
}
from
'./util/shadow-root'
;
/**
* @typedef {ArrowPointingDown|ArrowPointingUp} ArrowDirection
* Show the adder above the selection with an arrow pointing up at the
* selected text.
*/
export
enum
ArrowDirection
{
DOWN
=
1
,
UP
=
2
,
}
/**
* @typedef Target
* @prop {number} left - Offset from left edge of viewport.
* @prop {number} top - Offset from top edge of viewport.
* @prop {ArrowDirection} arrowDirection - Direction of the adder's arrow.
*/
type
Target
=
{
/** Offset from left edge of viewport */
left
:
number
;
/** Offset from top edge of viewport */
top
:
number
;
/** Direction of the adder's arrow */
arrowDirection
:
ArrowDirection
;
};
/** @param {number} pixels */
function
toPx
(
pixels
)
{
function
toPx
(
pixels
:
number
)
{
return
pixels
.
toString
()
+
'px'
;
}
...
...
@@ -44,14 +33,10 @@ const ARROW_H_MARGIN = 20;
/**
* Return the closest ancestor of `el` which has been positioned.
*
* If no ancestor has been positioned, returns the root element.
*
* @param {Element} el
* @return {Element}
*/
function
nearestPositionedAncestor
(
el
)
{
let
parentEl
=
/** @type {Element} */
(
el
.
parentElement
)
;
function
nearestPositionedAncestor
(
el
:
Element
):
Element
{
let
parentEl
=
el
.
parentElement
!
;
while
(
parentEl
.
parentElement
)
{
if
(
getComputedStyle
(
parentEl
).
position
!==
'static'
)
{
break
;
...
...
@@ -61,15 +46,14 @@ function nearestPositionedAncestor(el) {
return
parentEl
;
}
/**
* @typedef AdderOptions
* @prop {() => void} onAnnotate - Callback invoked when "Annotate" button is clicked
* @prop {() => void} onHighlight - Callback invoked when "Highlight" button is clicked
* @prop {(tags: string[]) => void} onShowAnnotations -
* Callback invoked when "Show" button is clicked
*
* @typedef {import('../types/annotator').Destroyable} Destroyable
*/
type
AdderOptions
=
{
/** Callback invoked when "Annotate" button is clicked */
onAnnotate
:
()
=>
void
;
/** Callback invoked when "Highlight" button is clicked */
onHighlight
:
()
=>
void
;
/** Callback invoked when "Show" button is clicked */
onShowAnnotations
:
(
tags
:
string
[])
=>
void
;
};
/**
* Container for the 'adder' toolbar which provides controls for the user to
...
...
@@ -79,20 +63,29 @@ function nearestPositionedAncestor(el) {
* the container for the toolbar that positions it on the page and isolates
* it from the page's styles using shadow DOM, and the `AdderToolbar` Preact
* component which actually renders the toolbar.
*
* @implements {Destroyable}
*/
export
class
Adder
{
export
class
Adder
implements
Destroyable
{
private
_outerContainer
:
HTMLElement
;
private
_shadowRoot
:
ShadowRoot
;
private
_view
:
Window
;
private
_isVisible
:
boolean
;
private
_arrowDirection
:
'up'
|
'down'
;
private
_onAnnotate
:
()
=>
void
;
private
_onHighlight
:
()
=>
void
;
private
_onShowAnnotations
:
(
tags
:
string
[])
=>
void
;
/** Annotation tags associated with the current selection. */
private
_annotationsForSelection
:
string
[];
/**
* Create the toolbar's container and hide it.
*
* The adder is initially hidden.
*
* @param
{HTMLElement}
element - The DOM element into which the adder will be created
* @param
{AdderOptions}
options - Options object specifying `onAnnotate` and `onHighlight`
* @param element - The DOM element into which the adder will be created
* @param options - Options object specifying `onAnnotate` and `onHighlight`
* event handlers.
*/
constructor
(
element
,
o
ptions
)
{
constructor
(
element
:
HTMLElement
,
options
:
AdderO
ptions
)
{
this
.
_outerContainer
=
document
.
createElement
(
'hypothesis-adder'
);
element
.
appendChild
(
this
.
_outerContainer
);
this
.
_shadowRoot
=
createShadowRoot
(
this
.
_outerContainer
);
...
...
@@ -105,34 +98,15 @@ export class Adder {
left
:
0
,
});
this
.
_view
=
/** @type {Window} */
(
element
.
ownerDocument
.
defaultView
);
this
.
_width
=
()
=>
{
const
firstChild
=
/** @type {Element} */
(
this
.
_shadowRoot
.
firstChild
);
return
firstChild
.
getBoundingClientRect
().
width
;
};
this
.
_height
=
()
=>
{
const
firstChild
=
/** @type {Element} */
(
this
.
_shadowRoot
.
firstChild
);
return
firstChild
.
getBoundingClientRect
().
height
;
};
this
.
_view
=
element
.
ownerDocument
.
defaultView
!
;
this
.
_isVisible
=
false
;
/** @type {'up'|'down'} */
this
.
_arrowDirection
=
'up'
;
this
.
_annotationsForSelection
=
[];
this
.
_onAnnotate
=
options
.
onAnnotate
;
this
.
_onHighlight
=
options
.
onHighlight
;
this
.
_onShowAnnotations
=
options
.
onShowAnnotations
;
/**
* Annotation tags associated with the current selection.
*
* @type {string[]}
*/
this
.
_annotationsForSelection
=
[];
this
.
_render
();
}
...
...
@@ -173,13 +147,12 @@ export class Adder {
* Display the adder in the best position in order to target the
* selected text in `selectionRect`.
*
* @param {DOMRect} selectionRect - The rect of text to target, in viewport
* coordinates.
* @param {boolean} isRTLselection - True if the selection was made
* rigth-to-left, such that the focus point is mosty likely at the
* top-left edge of `targetRect`.
* @param selectionRect - The rect of text to target, in viewport coordinates.
* @param isRTLselection - True if the selection was made right-to-left, such
* that the focus point is mostly likely at the top-left edge of
* `targetRect`.
*/
show
(
selectionRect
,
isRTLselectio
n
)
{
show
(
selectionRect
:
DOMRect
,
isRTLselection
:
boolea
n
)
{
const
{
left
,
top
,
arrowDirection
}
=
this
.
_calculateTarget
(
selectionRect
,
isRTLselection
...
...
@@ -187,11 +160,21 @@ export class Adder {
this
.
_showAt
(
left
,
top
);
this
.
_isVisible
=
true
;
this
.
_arrowDirection
=
arrowDirection
===
A
RROW_POINTING_
UP
?
'up'
:
'down'
;
this
.
_arrowDirection
=
arrowDirection
===
A
rrowDirection
.
UP
?
'up'
:
'down'
;
this
.
_render
();
}
private
_width
():
number
{
const
firstChild
=
this
.
_shadowRoot
.
firstChild
as
Element
;
return
firstChild
.
getBoundingClientRect
().
width
;
}
private
_height
():
number
{
const
firstChild
=
this
.
_shadowRoot
.
firstChild
as
Element
;
return
firstChild
.
getBoundingClientRect
().
height
;
}
/**
* Determine the best position for the Adder and its pointer-arrow.
* - Position the pointer-arrow near the end of the selection (where the user's
...
...
@@ -200,24 +183,25 @@ export class Adder {
* - Position the Adder below the selection (arrow pointing up) for LTR selections
* and above (arrow down) for RTL selections
*
* @param {DOMRect} selectionRect - The rect of text to target, in viewport
* coordinates.
* @param {boolean} isRTLselection - True if the selection was made
* rigth-to-left, such that the focus point is mosty likely at the
* top-left edge of `targetRect`.
* @return {Target}
* @param selectionRect - The rect of text to target, in viewport coordinates.
* @param isRTLselection - True if the selection was made right-to-left, such
* that the focus point is mostly likely at the top-left edge of
* `targetRect`.
*/
_calculateTarget
(
selectionRect
,
isRTLselection
)
{
private
_calculateTarget
(
selectionRect
:
DOMRect
,
isRTLselection
:
boolean
):
Target
{
// Set the initial arrow direction based on whether the selection was made
// forwards/upwards or downwards/backwards.
/** @type {ArrowDirection} */
let
a
rrowDirection
;
let
arrowDirection
:
A
rrowDirection
;
if
(
isRTLselection
&&
!
isTouchDevice
())
{
arrowDirection
=
A
RROW_POINTING_
DOWN
;
arrowDirection
=
A
rrowDirection
.
DOWN
;
}
else
{
// Render the adder below the selection for touch devices due to competing
// space with the native copy/paste bar that typical (not always) renders above
// the selection.
arrowDirection
=
A
RROW_POINTING_
UP
;
arrowDirection
=
A
rrowDirection
.
UP
;
}
let
top
;
let
left
;
...
...
@@ -241,14 +225,14 @@ export class Adder {
// bottom of the viewport.
if
(
selectionRect
.
top
-
adderHeight
<
0
&&
arrowDirection
===
A
RROW_POINTING_
DOWN
arrowDirection
===
A
rrowDirection
.
DOWN
)
{
arrowDirection
=
A
RROW_POINTING_
UP
;
arrowDirection
=
A
rrowDirection
.
UP
;
}
else
if
(
selectionRect
.
top
+
adderHeight
>
this
.
_view
.
innerHeight
)
{
arrowDirection
=
A
RROW_POINTING_
DOWN
;
arrowDirection
=
A
rrowDirection
.
DOWN
;
}
if
(
arrowDirection
===
A
RROW_POINTING_
UP
)
{
if
(
arrowDirection
===
A
rrowDirection
.
UP
)
{
top
=
selectionRect
.
top
+
selectionRect
.
height
+
...
...
@@ -272,11 +256,11 @@ export class Adder {
* Find a Z index value that will cause the adder to appear on top of any
* content in the document when the adder is shown at (left, top).
*
* @param
{number}
left - Horizontal offset from left edge of viewport.
* @param
{number}
top - Vertical offset from top edge of viewport.
* @return
{number} -
greatest zIndex (default value of 1)
* @param left - Horizontal offset from left edge of viewport.
* @param top - Vertical offset from top edge of viewport.
* @return greatest zIndex (default value of 1)
*/
_findZindex
(
left
,
top
)
{
private
_findZindex
(
left
:
number
,
top
:
number
):
number
{
if
(
document
.
elementsFromPoint
===
undefined
)
{
// In case of not being able to use `document.elementsFromPoint`,
// default to the large arbitrary number (2^15)
...
...
@@ -317,15 +301,15 @@ export class Adder {
* Show the adder at the given position and with the arrow pointing in
* `arrowDirection`.
*
* @param
{number}
left - Horizontal offset from left edge of viewport.
* @param
{number}
top - Vertical offset from top edge of viewport.
* @param left - Horizontal offset from left edge of viewport.
* @param top - Vertical offset from top edge of viewport.
*/
_showAt
(
left
,
top
)
{
private
_showAt
(
left
:
number
,
top
:
number
)
{
// Translate the (left, top) viewport coordinates into positions relative to
// the adder's nearest positioned ancestor (NPA).
//
// Typically the adder is a child of the `<body>` and the NPA is the root
// `<html>` element. However page styling may make the `<body>` positioned.
// Typically
,
the adder is a child of the `<body>` and the NPA is the root
// `<html>` element. However
,
page styling may make the `<body>` positioned.
// See https://github.com/hypothesis/client/issues/487.
const
positionedAncestor
=
nearestPositionedAncestor
(
this
.
_outerContainer
);
const
parentRect
=
positionedAncestor
.
getBoundingClientRect
();
...
...
@@ -339,9 +323,8 @@ export class Adder {
});
}
_render
()
{
/** @param {import('./components/AdderToolbar').Command} command */
const
handleCommand
=
command
=>
{
private
_render
()
{
const
handleCommand
=
(
command
:
Command
)
=>
{
switch
(
command
)
{
case
'annotate'
:
this
.
_onAnnotate
();
...
...
src/annotator/test/adder-test.js
View file @
38e4d947
import
{
act
}
from
'preact/test-utils'
;
import
{
mount
}
from
'enzyme'
;
import
{
Adder
,
ARROW_POINTING_UP
,
ARROW_POINTING_DOWN
,
$imports
,
}
from
'../adder'
;
import
{
Adder
,
ArrowDirection
,
$imports
}
from
'../adder'
;
function
rect
(
left
,
top
,
width
,
height
)
{
return
{
left
,
top
,
width
,
height
};
...
...
@@ -199,25 +194,25 @@ describe('Adder', () => {
it
(
'positions the adder below the selection if the selection is forwards'
,
()
=>
{
const
target
=
adder
.
_calculateTarget
(
rect
(
100
,
200
,
100
,
20
),
false
);
assert
.
isAbove
(
target
.
top
,
220
);
assert
.
equal
(
target
.
arrowDirection
,
A
RROW_POINTING_
UP
);
assert
.
equal
(
target
.
arrowDirection
,
A
rrowDirection
.
UP
);
});
it
(
'positions the adder above the selection if the selection is backwards'
,
()
=>
{
const
target
=
adder
.
_calculateTarget
(
rect
(
100
,
200
,
100
,
20
),
true
);
assert
.
isBelow
(
target
.
top
,
200
);
assert
.
equal
(
target
.
arrowDirection
,
A
RROW_POINTING_
DOWN
);
assert
.
equal
(
target
.
arrowDirection
,
A
rrowDirection
.
DOWN
);
});
it
(
'does not position the adder above the top of the viewport'
,
()
=>
{
const
target
=
adder
.
_calculateTarget
(
rect
(
100
,
-
100
,
100
,
20
),
false
);
assert
.
isAtLeast
(
target
.
top
,
0
);
assert
.
equal
(
target
.
arrowDirection
,
A
RROW_POINTING_
UP
);
assert
.
equal
(
target
.
arrowDirection
,
A
rrowDirection
.
UP
);
});
it
(
'does not position the adder above the top of the viewport even when selection is backwards'
,
()
=>
{
const
target
=
adder
.
_calculateTarget
(
rect
(
100
,
-
100
,
100
,
20
),
true
);
assert
.
isAtLeast
(
target
.
top
,
0
);
assert
.
equal
(
target
.
arrowDirection
,
A
RROW_POINTING_
UP
);
assert
.
equal
(
target
.
arrowDirection
,
A
rrowDirection
.
UP
);
});
it
(
'does not position the adder below the bottom of the viewport'
,
()
=>
{
...
...
@@ -252,7 +247,7 @@ describe('Adder', () => {
});
const
target
=
adder
.
_calculateTarget
(
rect
(
100
,
200
,
100
,
20
),
true
);
assert
.
isAbove
(
target
.
top
,
220
);
assert
.
equal
(
target
.
arrowDirection
,
A
RROW_POINTING_
UP
);
assert
.
equal
(
target
.
arrowDirection
,
A
rrowDirection
.
UP
);
});
});
});
...
...
@@ -337,7 +332,7 @@ describe('Adder', () => {
describe
(
'#_showAt'
,
()
=>
{
context
(
'when the document and body elements have no offset'
,
()
=>
{
it
(
'shows adder at target position'
,
()
=>
{
adder
.
_showAt
(
100
,
100
,
A
RROW_POINTING_
UP
);
adder
.
_showAt
(
100
,
100
,
A
rrowDirection
.
UP
);
const
{
left
,
top
}
=
adderRect
();
assert
.
equal
(
left
,
100
);
...
...
@@ -355,7 +350,7 @@ describe('Adder', () => {
});
it
(
'shows adder at target position'
,
()
=>
{
adder
.
_showAt
(
100
,
100
,
A
RROW_POINTING_
UP
);
adder
.
_showAt
(
100
,
100
,
A
rrowDirection
.
UP
);
const
{
left
,
top
}
=
adderRect
();
assert
.
equal
(
left
,
100
);
...
...
@@ -373,7 +368,7 @@ describe('Adder', () => {
});
it
(
'shows adder at target position when document element is offset'
,
()
=>
{
adder
.
_showAt
(
100
,
100
,
A
RROW_POINTING_
UP
);
adder
.
_showAt
(
100
,
100
,
A
rrowDirection
.
UP
);
const
{
left
,
top
}
=
adderRect
();
assert
.
equal
(
left
,
100
);
...
...
@@ -399,7 +394,7 @@ describe('Adder', () => {
describe
(
'#hide'
,
()
=>
{
it
(
'shows the container in the correct location'
,
()
=>
{
adder
.
_showAt
(
100
,
100
,
A
RROW_POINTING_
UP
);
adder
.
_showAt
(
100
,
100
,
A
rrowDirection
.
UP
);
let
pos
=
adderRect
();
assert
.
equal
(
pos
.
left
,
100
);
...
...
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