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
d78c29cd
Unverified
Commit
d78c29cd
authored
Jan 16, 2020
by
Robert Knight
Committed by
GitHub
Jan 16, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1682 from hypothesis/convert-highlighter-to-js
Convert text highlighter to JS
parents
b72e00eb
d7bf8116
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
191 additions
and
181 deletions
+191
-181
highlighter.js
src/annotator/highlighter.js
+75
-0
index.coffee
src/annotator/highlighter/dom-wrap-highlighter/index.coffee
+0
-43
test.coffee
src/annotator/highlighter/dom-wrap-highlighter/test.coffee
+0
-95
index.js
src/annotator/highlighter/index.js
+0
-27
index.js
src/annotator/highlighter/overlay-highlighter/index.js
+0
-16
highlighter-test.js
src/annotator/test/highlighter-test.js
+116
-0
No files found.
src/annotator/highlighter.js
0 → 100644
View file @
d78c29cd
import
$
from
'jquery'
;
/**
* Wraps the DOM Nodes within the provided range with a highlight
* element of the specified class and returns the highlight Elements.
*
* @param {NormalizedRange} normedRange - Range to be highlighted.
* @param {string} cssClass - A CSS class to use for the highlight (default: 'annotator-hl')
* @return {HTMLElement[]} - Elements wrapping text in `normedRange` to add a highlight effect
*/
export
function
highlightRange
(
normedRange
,
cssClass
=
'annotator-hl'
)
{
const
white
=
/^
\s
*$/
;
// A custom element name is used here rather than `<span>` to reduce the
// likelihood of highlights being hidden by page styling.
const
hl
=
$
(
`<hypothesis-highlight class='
${
cssClass
}
'></hypothesis-highlight>`
);
// Ignore text nodes that contain only whitespace characters. This prevents
// spans being injected between elements that can only contain a restricted
// subset of nodes such as table rows and lists. This does mean that there
// may be the odd abandoned whitespace node in a paragraph that is skipped
// but better than breaking table layouts.
const
nodes
=
$
(
normedRange
.
textNodes
()).
filter
(
function
()
{
return
!
white
.
test
(
this
.
nodeValue
);
});
return
nodes
.
wrap
(
hl
)
.
parent
()
.
toArray
();
}
/**
* Remove highlights from a range previously highlighted with `highlightRange`.
*
* @param {HTMLElement[]} highlights - The highlight elements returned by `highlightRange`
*/
export
function
removeHighlights
(
highlights
)
{
for
(
let
h
of
highlights
)
{
if
(
h
.
parentNode
)
{
$
(
h
).
replaceWith
(
h
.
childNodes
);
}
}
}
/**
* @typedef Rect
* @prop {number} top
* @prop {number} left
* @prop {number} bottom
* @prop {number} right
*/
/**
* Get the bounding client rectangle of a collection in viewport coordinates.
* Unfortunately, Chrome has issues ([1]) with Range.getBoundingClient rect or we
* could just use that.
*
* [1] https://bugs.chromium.org/p/chromium/issues/detail?id=324437
*
* @param {HTMLElement[]} collection
* @return {Rect}
*/
export
function
getBoundingClientRect
(
collection
)
{
// Reduce the client rectangles of the highlights to a bounding box
const
rects
=
collection
.
map
(
n
=>
n
.
getBoundingClientRect
());
return
rects
.
reduce
((
acc
,
r
)
=>
({
top
:
Math
.
min
(
acc
.
top
,
r
.
top
),
left
:
Math
.
min
(
acc
.
left
,
r
.
left
),
bottom
:
Math
.
max
(
acc
.
bottom
,
r
.
bottom
),
right
:
Math
.
max
(
acc
.
right
,
r
.
right
),
}));
}
src/annotator/highlighter/dom-wrap-highlighter/index.coffee
deleted
100644 → 0
View file @
b72e00eb
$
=
require
(
'jquery'
)
# Public: Wraps the DOM Nodes within the provided range with a highlight
# element of the specified class and returns the highlight Elements.
#
# normedRange - A NormalizedRange to be highlighted.
# cssClass - A CSS class to use for the highlight (default: 'annotator-hl')
#
# Returns an array of highlight Elements.
exports
.
highlightRange
=
(
normedRange
,
cssClass
=
'annotator-hl'
)
->
white
=
/^\s*$/
# A custom element name is used here rather than `<span>` to reduce the
# likelihood of highlights being hidden by page styling.
hl
=
$
(
"<hypothesis-highlight class='
#{
cssClass
}
'></hypothesis-highlight>"
)
# Ignore text nodes that contain only whitespace characters. This prevents
# spans being injected between elements that can only contain a restricted
# subset of nodes such as table rows and lists. This does mean that there
# may be the odd abandoned whitespace node in a paragraph that is skipped
# but better than breaking table layouts.
nodes
=
$
(
normedRange
.
textNodes
()).
filter
((
i
)
->
not
white
.
test
@
nodeValue
)
return
nodes
.
wrap
(
hl
).
parent
().
toArray
()
exports
.
removeHighlights
=
(
highlights
)
->
for
h
in
highlights
when
h
.
parentNode
?
$
(
h
).
replaceWith
(
h
.
childNodes
)
# Get the bounding client rectangle of a collection in viewport coordinates.
# Unfortunately, Chrome has issues[1] with Range.getBoundingClient rect or we
# could just use that.
# [1] https://code.google.com/p/chromium/issues/detail?id=324437
exports
.
getBoundingClientRect
=
(
collection
)
->
# Reduce the client rectangles of the highlights to a bounding box
rects
=
collection
.
map
((
n
)
->
n
.
getBoundingClientRect
())
return
rects
.
reduce
(
acc
,
r
)
->
top
:
Math
.
min
(
acc
.
top
,
r
.
top
)
left
:
Math
.
min
(
acc
.
left
,
r
.
left
)
bottom
:
Math
.
max
(
acc
.
bottom
,
r
.
bottom
)
right
:
Math
.
max
(
acc
.
right
,
r
.
right
)
src/annotator/highlighter/dom-wrap-highlighter/test.coffee
deleted
100644 → 0
View file @
b72e00eb
Range
=
require
(
'../../anchoring/range'
)
$
=
require
(
'jquery'
)
highlighter
=
require
(
'./index'
)
describe
"highlightRange"
,
->
it
'wraps a highlight span around the given range'
,
->
txt
=
document
.
createTextNode
(
'test highlight span'
)
el
=
document
.
createElement
(
'span'
)
el
.
appendChild
(
txt
)
r
=
new
Range
.
NormalizedRange
({
commonAncestor
:
el
,
start
:
txt
,
end
:
txt
})
result
=
highlighter
.
highlightRange
(
r
)
assert
.
equal
(
result
.
length
,
1
)
assert
.
strictEqual
(
el
.
childNodes
[
0
],
result
[
0
])
assert
.
equal
(
result
[
0
].
nodeName
,
'HYPOTHESIS-HIGHLIGHT'
);
assert
.
isTrue
(
result
[
0
].
classList
.
contains
(
'annotator-hl'
))
it
'skips text nodes that are only white space'
,
->
txt
=
document
.
createTextNode
(
'one'
)
blank
=
document
.
createTextNode
(
' '
)
txt2
=
document
.
createTextNode
(
'two'
)
el
=
document
.
createElement
(
'span'
)
el
.
appendChild
(
txt
)
el
.
appendChild
(
blank
)
el
.
appendChild
(
txt2
)
r
=
new
Range
.
NormalizedRange
({
commonAncestor
:
el
,
start
:
txt
,
end
:
txt2
})
result
=
highlighter
.
highlightRange
(
r
)
assert
.
equal
(
result
.
length
,
2
)
assert
.
strictEqual
(
el
.
childNodes
[
0
],
result
[
0
])
assert
.
strictEqual
(
el
.
childNodes
[
2
],
result
[
1
])
describe
'removeHighlights'
,
->
it
'unwraps all the elements'
,
->
txt
=
document
.
createTextNode
(
'word'
)
el
=
document
.
createElement
(
'span'
)
hl
=
document
.
createElement
(
'span'
)
div
=
document
.
createElement
(
'div'
)
el
.
appendChild
(
txt
)
hl
.
appendChild
(
el
)
div
.
appendChild
(
hl
)
highlighter
.
removeHighlights
([
hl
])
assert
.
isNull
(
hl
.
parentNode
)
assert
.
strictEqual
(
el
.
parentNode
,
div
)
it
'does not fail on nodes with no parent'
,
->
txt
=
document
.
createTextNode
(
'no parent'
)
hl
=
document
.
createElement
(
'span'
)
hl
.
appendChild
(
txt
)
highlighter
.
removeHighlights
([
hl
])
describe
"getBoundingClientRect"
,
->
it
'returns the bounding box of all the highlight client rectangles'
,
->
rects
=
[
{
top
:
20
left
:
15
bottom
:
30
right
:
25
}
{
top
:
10
left
:
15
bottom
:
20
right
:
25
}
{
top
:
15
left
:
20
bottom
:
25
right
:
30
}
{
top
:
15
left
:
10
bottom
:
25
right
:
20
}
]
fakeHighlights
=
rects
.
map
(
r
)
->
return
getBoundingClientRect
:
->
r
result
=
highlighter
.
getBoundingClientRect
(
fakeHighlights
)
assert
.
equal
(
result
.
left
,
10
)
assert
.
equal
(
result
.
top
,
10
)
assert
.
equal
(
result
.
right
,
30
)
assert
.
equal
(
result
.
bottom
,
30
)
src/annotator/highlighter/index.js
deleted
100644 → 0
View file @
b72e00eb
const
domWrapHighlighter
=
require
(
'./dom-wrap-highlighter'
);
const
overlayHighlighter
=
require
(
'./overlay-highlighter'
);
const
features
=
require
(
'../features'
);
// we need a facade for the highlighter interface
// that will let us lazy check the overlay_highlighter feature
// flag and later determine which interface should be used.
const
highlighterFacade
=
{};
let
overlayFlagEnabled
;
Object
.
keys
(
domWrapHighlighter
).
forEach
(
methodName
=>
{
highlighterFacade
[
methodName
]
=
(...
args
)
=>
{
// lazy check the value but we will
// use that first value as the rule throughout
// the in memory session
if
(
overlayFlagEnabled
===
undefined
)
{
overlayFlagEnabled
=
features
.
flagEnabled
(
'overlay_highlighter'
);
}
const
method
=
overlayFlagEnabled
?
overlayHighlighter
[
methodName
]
:
domWrapHighlighter
[
methodName
];
return
method
.
apply
(
null
,
args
);
};
});
module
.
exports
=
highlighterFacade
;
src/annotator/highlighter/overlay-highlighter/index.js
deleted
100644 → 0
View file @
b72e00eb
module
.
exports
=
{
highlightRange
:
()
=>
{
// eslint-disable-next-line no-console
console
.
log
(
'highlightRange not implemented'
);
},
removeHighlights
:
()
=>
{
// eslint-disable-next-line no-console
console
.
log
(
'removeHighlights not implemented'
);
},
getBoundingClientRect
:
()
=>
{
// eslint-disable-next-line no-console
console
.
log
(
'getBoundingClientRect not implemented'
);
},
};
src/annotator/test/highlighter-test.js
0 → 100644
View file @
d78c29cd
import
Range
from
'../anchoring/range'
;
import
{
highlightRange
,
removeHighlights
,
getBoundingClientRect
,
}
from
'../highlighter'
;
describe
(
'annotator/highlighter'
,
()
=>
{
describe
(
'highlightRange'
,
()
=>
{
it
(
'wraps a highlight span around the given range'
,
()
=>
{
const
txt
=
document
.
createTextNode
(
'test highlight span'
);
const
el
=
document
.
createElement
(
'span'
);
el
.
appendChild
(
txt
);
const
r
=
new
Range
.
NormalizedRange
({
commonAncestor
:
el
,
start
:
txt
,
end
:
txt
,
});
const
result
=
highlightRange
(
r
);
assert
.
equal
(
result
.
length
,
1
);
assert
.
strictEqual
(
el
.
childNodes
[
0
],
result
[
0
]);
assert
.
equal
(
result
[
0
].
nodeName
,
'HYPOTHESIS-HIGHLIGHT'
);
assert
.
isTrue
(
result
[
0
].
classList
.
contains
(
'annotator-hl'
));
});
it
(
'skips text nodes that are only white space'
,
()
=>
{
const
txt
=
document
.
createTextNode
(
'one'
);
const
blank
=
document
.
createTextNode
(
' '
);
const
txt2
=
document
.
createTextNode
(
'two'
);
const
el
=
document
.
createElement
(
'span'
);
el
.
appendChild
(
txt
);
el
.
appendChild
(
blank
);
el
.
appendChild
(
txt2
);
const
r
=
new
Range
.
NormalizedRange
({
commonAncestor
:
el
,
start
:
txt
,
end
:
txt2
,
});
const
result
=
highlightRange
(
r
);
assert
.
equal
(
result
.
length
,
2
);
assert
.
strictEqual
(
el
.
childNodes
[
0
],
result
[
0
]);
assert
.
strictEqual
(
el
.
childNodes
[
2
],
result
[
1
]);
});
});
describe
(
'removeHighlights'
,
()
=>
{
it
(
'unwraps all the elements'
,
()
=>
{
const
txt
=
document
.
createTextNode
(
'word'
);
const
el
=
document
.
createElement
(
'span'
);
const
hl
=
document
.
createElement
(
'span'
);
const
div
=
document
.
createElement
(
'div'
);
el
.
appendChild
(
txt
);
hl
.
appendChild
(
el
);
div
.
appendChild
(
hl
);
removeHighlights
([
hl
]);
assert
.
isNull
(
hl
.
parentNode
);
assert
.
strictEqual
(
el
.
parentNode
,
div
);
});
it
(
'does not fail on nodes with no parent'
,
()
=>
{
const
txt
=
document
.
createTextNode
(
'no parent'
);
const
hl
=
document
.
createElement
(
'span'
);
hl
.
appendChild
(
txt
);
removeHighlights
([
hl
]);
});
});
describe
(
'getBoundingClientRect'
,
()
=>
{
it
(
'returns the bounding box of all the highlight client rectangles'
,
()
=>
{
const
rects
=
[
{
top
:
20
,
left
:
15
,
bottom
:
30
,
right
:
25
,
},
{
top
:
10
,
left
:
15
,
bottom
:
20
,
right
:
25
,
},
{
top
:
15
,
left
:
20
,
bottom
:
25
,
right
:
30
,
},
{
top
:
15
,
left
:
10
,
bottom
:
25
,
right
:
20
,
},
];
const
fakeHighlights
=
rects
.
map
(
r
=>
{
return
{
getBoundingClientRect
:
()
=>
r
};
});
const
result
=
getBoundingClientRect
(
fakeHighlights
);
assert
.
equal
(
result
.
left
,
10
);
assert
.
equal
(
result
.
top
,
10
);
assert
.
equal
(
result
.
right
,
30
);
assert
.
equal
(
result
.
bottom
,
30
);
});
});
});
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