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
04cc7f9a
Commit
04cc7f9a
authored
Apr 19, 2023
by
Alejandro Celaya
Committed by
Alejandro Celaya
Apr 19, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Migrate html-side-by-side module to TS
parent
9390cdf2
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
50 additions
and
65 deletions
+50
-65
html-side-by-side.ts
src/annotator/integrations/html-side-by-side.ts
+41
-61
range-util.ts
src/annotator/range-util.ts
+2
-4
node.ts
src/annotator/util/node.ts
+7
-0
No files found.
src/annotator/integrations/html-side-by-side.
j
s
→
src/annotator/integrations/html-side-by-side.
t
s
View file @
04cc7f9a
import
{
rectContains
,
rectIntersects
}
from
'../util/geometry'
;
import
{
nodeIsElement
,
nodeIsText
}
from
'../util/node'
;
/**
* CSS selectors used to find elements that are considered potentially part
...
...
@@ -14,18 +15,14 @@ const contentSelectors = [
/**
* Attempt to guess the region of the page that contains the main content.
*
* @param {Element} root
* @return {{ left: number, right: number }|null} -
* The left/right content margins or `null` if they could not be determined
* @return The left/right content margins or `null` if they could not be determined
*/
export
function
guessMainContentArea
(
root
)
{
export
function
guessMainContentArea
(
root
:
Element
):
{
left
:
number
;
right
:
number
}
|
null
{
// Maps of (margin X coord, votes) for margin positions.
/** @type {Map<number,number>} */
const
leftMarginVotes
=
new
Map
();
/** @type {Map<number,number>} */
const
rightMarginVotes
=
new
Map
();
const
leftMarginVotes
=
new
Map
<
number
,
number
>
();
const
rightMarginVotes
=
new
Map
<
number
,
number
>
();
// Gather data about the paragraphs of text in the document.
//
...
...
@@ -37,7 +34,7 @@ export function guessMainContentArea(root) {
.
map
(
p
=>
{
// Gather some data about them.
const
rect
=
p
.
getBoundingClientRect
();
const
textLength
=
/** @type {string} */
(
p
.
textContent
)
.
length
;
const
textLength
=
p
.
textContent
!
.
length
;
return
{
rect
,
textLength
};
})
.
filter
(({
rect
})
=>
{
...
...
@@ -76,17 +73,12 @@ export function guessMainContentArea(root) {
return
{
left
:
leftPos
,
right
:
rightPos
};
}
/** @type {Range} */
let
textRectRange
;
let
textRectRange
:
Range
;
/**
* Return the viewport-relative rect occupied by part of a text node.
*
* @param {Text} text
* @param {number} start
* @param {number} end
*/
function
textRect
(
text
,
start
=
0
,
end
=
text
.
data
.
length
)
{
function
textRect
(
text
:
Text
,
start
=
0
,
end
:
number
=
text
.
data
.
length
)
{
if
(
!
textRectRange
)
{
// Allocate a range only on the first call to avoid the overhead of
// constructing and maintaining a large number of live ranges.
...
...
@@ -97,8 +89,7 @@ function textRect(text, start = 0, end = text.data.length) {
return
textRectRange
.
getBoundingClientRect
();
}
/** @param {Element} element */
function
hasFixedPosition
(
element
)
{
function
hasFixedPosition
(
element
:
Element
)
{
switch
(
getComputedStyle
(
element
).
position
)
{
case
'fixed'
:
case
'sticky'
:
...
...
@@ -112,10 +103,8 @@ function hasFixedPosition(element) {
* Return the bounding rect that contains the element's content. Unlike
* `Element.getBoundingClientRect`, this includes content which overflows
* the element's specified size.
*
* @param {Element} element
*/
function
elementContentRect
(
element
)
{
function
elementContentRect
(
element
:
Element
)
{
const
rect
=
element
.
getBoundingClientRect
();
rect
.
x
-=
element
.
scrollLeft
;
rect
.
y
-=
element
.
scrollTop
;
...
...
@@ -127,33 +116,29 @@ function elementContentRect(element) {
/**
* Yield all the text node descendants of `root` that intersect `rect`.
*
* @param {Element} root
* @param {DOMRect} rect
* @param {(el: Element) => boolean} shouldVisit - Optional filter that determines
* whether to visit a subtree
* @return {Generator<Text>}
* @param shouldVisit - Optional filter that determines whether to visit a subtree
*/
function
*
textNodesInRect
(
root
,
rect
,
shouldVisit
=
()
=>
true
)
{
/** @type {Node|null} */
let
node
=
root
.
firstChild
;
function
*
textNodesInRect
(
root
:
Element
,
rect
:
DOMRect
,
shouldVisit
:
(
el
:
Element
)
=>
boolean
=
()
=>
true
):
Generator
<
Text
>
{
let
node
:
Node
|
null
=
root
.
firstChild
;
while
(
node
)
{
if
(
node
.
nodeType
===
Node
.
ELEMENT_NODE
)
{
const
element
=
/** @type {Element} */
(
node
);
if
(
nodeIsElement
(
node
))
{
const
contentIntersectsRect
=
rectIntersects
(
elementContentRect
(
element
),
elementContentRect
(
node
),
rect
);
// Only examine subtrees which are visible.
if
(
shouldVisit
(
element
)
&&
contentIntersectsRect
)
{
yield
*
textNodesInRect
(
element
,
rect
,
shouldVisit
);
if
(
shouldVisit
(
node
)
&&
contentIntersectsRect
)
{
yield
*
textNodesInRect
(
node
,
rect
,
shouldVisit
);
}
}
else
if
(
node
.
nodeType
===
Node
.
TEXT_NODE
)
{
const
text
=
/** @type {Text} */
(
node
);
}
else
if
(
nodeIsText
(
node
))
{
// Skip over text nodes which are entirely outside the viewport or empty.
if
(
rectIntersects
(
textRect
(
text
),
rect
))
{
yield
text
;
if
(
rectIntersects
(
textRect
(
node
),
rect
))
{
yield
node
;
}
}
node
=
node
.
nextSibling
;
...
...
@@ -164,25 +149,21 @@ function* textNodesInRect(root, rect, shouldVisit = () => true) {
* Find content within an element to use as an anchor when applying a layout
* change to the document.
*
* @param {Element} root
* @param {DOMRect} viewport
* @return {Range|null} - Range to use as an anchor or `null` if a suitable
* range could not be found
* @return Range to use as an anchor or `null` if a suitable range could not be found
*/
function
getScrollAnchor
(
root
,
viewport
)
{
function
getScrollAnchor
(
root
:
Element
,
viewport
:
DOMRect
):
Range
|
null
{
// Range representing the content whose position within the viewport we will
// try to maintain after running the callback.
let
anchorRange
=
/** @type {Range|null} */
(
null
)
;
let
anchorRange
:
Range
|
null
=
null
;
// Find the first word (non-whitespace substring of a text node) that is fully
// visible in the viewport.
// Text inside fixed-position elements is ignored because its position won't
// be affected by a layout change and so it makes a poor scroll anchor.
/** @param {Element} el */
const
shouldVisit
=
el
=>
!
hasFixedPosition
(
el
);
const
shouldVisit
=
(
el
:
Element
)
=>
!
hasFixedPosition
(
el
);
textNodeLoop
:
for
(
le
t
textNode
of
textNodesInRect
(
textNodeLoop
:
for
(
cons
t
textNode
of
textNodesInRect
(
root
,
viewport
,
shouldVisit
...
...
@@ -190,7 +171,7 @@ function getScrollAnchor(root, viewport) {
let
textLen
=
0
;
// Visit all the non-whitespace substrings of the text node.
for
(
le
t
word
of
textNode
.
data
.
split
(
/
\b
/
))
{
for
(
cons
t
word
of
textNode
.
data
.
split
(
/
\b
/
))
{
if
(
/
\S
/
.
test
(
word
))
{
const
start
=
textLen
;
const
end
=
textLen
+
word
.
length
;
...
...
@@ -217,20 +198,19 @@ function getScrollAnchor(root, viewport) {
* and tries to preserve the position of this content within the viewport
* after the callback is invoked.
*
* @param {() => void} callback - Callback that will apply the layout change
* @param {Element} [scrollRoot]
* @param {DOMRect} [viewport] - Area to consider "in the viewport". Defaults to
* the viewport of the current window.
* @return {number} - Amount by which the scroll position was adjusted to keep
* the anchored content in view
* @param callback - Callback that will apply the layout change
* @param [viewport] - Area to consider "in the viewport". Defaults to the
* viewport of the current window.
* @return Amount by which the scroll position was adjusted to keep the anchored
* content in view
*/
export
function
preserveScrollPosition
(
callback
,
callback
:
()
=>
void
,
/* istanbul ignore next */
scrollRoot
=
document
.
documentElement
,
scrollRoot
:
Element
=
document
.
documentElement
,
/* istanbul ignore next */
viewport
=
new
DOMRect
(
0
,
0
,
window
.
innerWidth
,
window
.
innerHeight
)
)
{
viewport
:
DOMRect
=
new
DOMRect
(
0
,
0
,
window
.
innerWidth
,
window
.
innerHeight
)
)
:
number
{
const
anchor
=
getScrollAnchor
(
scrollRoot
,
viewport
);
if
(
!
anchor
)
{
callback
();
...
...
src/annotator/range-util.ts
View file @
04cc7f9a
import
{
nodeIsText
}
from
'./util/node'
;
/**
* Returns true if the start point of a selection occurs after the end point,
* in document order.
...
...
@@ -51,10 +53,6 @@ export function forEachNodeInRange(range: Range, callback: (n: Node) => void) {
}
}
function
nodeIsText
(
node
:
Node
):
node
is
Text
{
return
node
.
nodeType
===
Node
.
TEXT_NODE
;
}
function
textNodeContainsText
(
textNode
:
Text
):
boolean
{
const
whitespaceOnly
=
/^
\s
*$/
;
return
!
textNode
.
textContent
!
.
match
(
whitespaceOnly
);
...
...
src/annotator/util/node.ts
0 → 100644
View file @
04cc7f9a
export
function
nodeIsElement
(
node
:
Node
):
node
is
Element
{
return
node
.
nodeType
===
Node
.
ELEMENT_NODE
;
}
export
function
nodeIsText
(
node
:
Node
):
node
is
Text
{
return
node
.
nodeType
===
Node
.
TEXT_NODE
;
}
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