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