rangy
rangy copied to clipboard
The highlighter restore won't work perfectly.
My highlighter css name is mydict_highlighted
THe three highlightened words are type:textContent|14682$14685$2$mydict_highlighted$|14705$14715$1$mydict_highlighted$|14821$14829$3$mydict_highlighted$
The demo page is http://www.imdb.com/title/tt2543312/
After reloading the page, the result is so strange
restoreSelection() {
var docURL = getDocumentURL();
const storedWords = localStorage[docURL]
if (storedWords) {
this.highlighter.deserialize(storedWords);
}
}
Hi, Chome or IE?
Hi it's on chrome. i'm wirting a chrome extension
I had issues with highlighter in ie edge. Now i use classApllier
rangy.getSelection().moveToBookmark(bookmark);
var selection = rangy.getSelection();
classApplier.applyToSelection();
Maybe you inserted some html code in this section? It can affects position start and end.
@veremeenko I think the bug is due to the page content is dynamically generated by javascript. for example, if you try to highlight text on amazon product page and deseriliazed by reloading the page. It will give you the buggy result. I guess that's because the xpath for the selected text is inconsistent. any good sollution?
@poc7667 I have switched from the highlighter to bookmarks. It helped me.
@veremeenko will try it later, thanks
Any updates on this issue, yet? I have the same issue, only that in my case the wrong are is highlighted when I refresh the page. The contents and the div exists when the page reloads, but for some reason, it is highlighting the wrong contents. It works when I put in a settimeout for 2 seconds (1 second is not enough).
And I am facing this issue in chrome.
@veremeenko will try it later, thanks
Hi, I'm curious if the bookmarks solved the problem? Because I'm having the same problem now
I found the issue and the solution to this. This issue is because even though you initialize rangy to a div, the highlight range you get is taken from the start of the html document. If there are some dynamic contents in the divs before the highlighter div, this will impact the highlight.
My solution is to find the offset of the highlighter div with respect to the html document. Then, I subtract the offset from the highlight offset before sending it to the database. Similiarly, when I get the highlight range from the database, I add the offset before deserializing the highlight.
I am using the textrange module.
Method to find Offset range:
getHighlighterRangeOffset() {
if (this.highlighter) {
const converter = this.highlighter.converter;
const pageContainer = this.elementRef.nativeElement.querySelector(
'#highlight-container'
);
if (pageContainer) {
const containerRange = rangy.createRange(document);
if (containerRange) {
containerRange.setStart(pageContainer, 0);
const containerCharRange = converter.rangeToCharacterRange(
containerRange
);
const rangeOffset = containerCharRange.start;
return parseInt(rangeOffset, 10);
}
}
}
return 0;
}
Where
this.highlighter = this.rangy.createHighlighter(
this.elementRef.nativeElement.querySelector('#highlight-container'),
'TextRange'
);
Method to serialize with respective to the offset. Input is the result of 'this.highlighter.serialize()'
serializeHighlighterWithOffset(highlighterRange: string) {
if (highlighterRange) {
const highLighParts = highlighterRange.split('|');
const module = highLighParts[0];
if (module && module === 'type:TextRange') {
const highlightContainerOffset = this.getHighlighterRangeOffset();
let ranges = highLighParts.filter((part, index) => index > 0);
if (ranges && ranges.filter((x) => x).length > 0) {
ranges = ranges.map((range) => {
const rangeParts = range.split('$');
rangeParts[0] = (
parseInt(rangeParts[0], 10) - highlightContainerOffset
).toString();
rangeParts[1] = (
parseInt(rangeParts[1], 10) - highlightContainerOffset
).toString();
return rangeParts.join('$');
});
return 'type:TextRange|' + ranges.join('|');
}
}
}
return highlighterRange;
}
Method to deserialize with respective to the offset. Input is the highlight range saved in the database'
deserializeHighlighterWithOffset(highlighterRange: string) {
if (highlighterRange) {
const highLighParts = highlighterRange.split('|');
const module = highLighParts[0];
if (module && module === 'type:TextRange') {
const highlightContainerOffset = this.getHighlighterRangeOffset();
let ranges = highLighParts.filter((part, index) => index > 0);
if (ranges && ranges.filter((x) => x).length > 0) {
ranges = ranges.map((range) => {
const rangeParts = range.split('$');
rangeParts[0] = (
parseInt(rangeParts[0], 10) + highlightContainerOffset
).toString();
rangeParts[1] = (
parseInt(rangeParts[1], 10) + highlightContainerOffset
).toString();
return rangeParts.join('$');
});
return 'type:TextRange|' + ranges.join('|');
}
}
}
return highlighterRange;
}
}
Hope this helps
I found the issue and the solution to this. This issue is because even though you initialize rangy to a div, the highlight range you get is taken from the start of the html document. If there are some dynamic contents in the divs before the highlighter div, this will impact the highlight.
My solution is to find the offset of the highlighter div with respect to the html document. Then, I subtract the offset from the highlight offset before sending it to the database. Similiarly, when I get the highlight range from the database, I add the offset before deserializing the highlight.
I am using the textrange module.
Method to find Offset range:
getHighlighterRangeOffset() { if (this.highlighter) { const converter = this.highlighter.converter; const pageContainer = this.elementRef.nativeElement.querySelector( '#highlight-container' ); if (pageContainer) { const containerRange = rangy.createRange(document); if (containerRange) { containerRange.setStart(pageContainer, 0); const containerCharRange = converter.rangeToCharacterRange( containerRange ); const rangeOffset = containerCharRange.start; return parseInt(rangeOffset, 10); } } } return 0; }
Where
this.highlighter = this.rangy.createHighlighter( this.elementRef.nativeElement.querySelector('#highlight-container'), 'TextRange' );
Method to serialize with respective to the offset. Input is the result of 'this.highlighter.serialize()'
serializeHighlighterWithOffset(highlighterRange: string) { if (highlighterRange) { const highLighParts = highlighterRange.split('|'); const module = highLighParts[0]; if (module && module === 'type:TextRange') { const highlightContainerOffset = this.getHighlighterRangeOffset(); let ranges = highLighParts.filter((part, index) => index > 0); if (ranges && ranges.filter((x) => x).length > 0) { ranges = ranges.map((range) => { const rangeParts = range.split('$'); rangeParts[0] = ( parseInt(rangeParts[0], 10) - highlightContainerOffset ).toString(); rangeParts[1] = ( parseInt(rangeParts[1], 10) - highlightContainerOffset ).toString(); return rangeParts.join('$'); }); return 'type:TextRange|' + ranges.join('|'); } } } return highlighterRange; }
Method to deserialize with respective to the offset. Input is the highlight range saved in the database'
deserializeHighlighterWithOffset(highlighterRange: string) { if (highlighterRange) { const highLighParts = highlighterRange.split('|'); const module = highLighParts[0]; if (module && module === 'type:TextRange') { const highlightContainerOffset = this.getHighlighterRangeOffset(); let ranges = highLighParts.filter((part, index) => index > 0); if (ranges && ranges.filter((x) => x).length > 0) { ranges = ranges.map((range) => { const rangeParts = range.split('$'); rangeParts[0] = ( parseInt(rangeParts[0], 10) + highlightContainerOffset ).toString(); rangeParts[1] = ( parseInt(rangeParts[1], 10) + highlightContainerOffset ).toString(); return rangeParts.join('$'); }); return 'type:TextRange|' + ranges.join('|'); } } } return highlighterRange; } }
Hope this helps
@senkaplan Thanks for the reply! But it's not working for me as the Offset i got is always 0. I don't have a particular container in my case (it's a Chrome Extension and highlighting can be done on any webpages; Eg. https://vuejs.org/v2/guide/ ) so I thought I changed the this.elementRef.nativeElement.querySelector('#highlight-container')
to document.getElementsByTagName("BODY")[0]
. Any advice?
@ryzalyusoff
Despite adding this code, I still have a settimeout of 2000ms to deserialize the highlight range.
The 2000ms delay is after the page has loaded and the content is loaded and the angular change detection was run.
You can debug it by doing the deserialization yourself once the page is completely loaded and see if that has an impact. If it works, then you have to introduce a delay or check for the page load completion.
May be this will help too.
Hey Hi, I am also facing same issue. I have tried so many ways to fix and spent lot of time to check this. How to check page load completion ? Because I have tried below, but its not working here as these onload will just verify DOM elements are loaded/not. When we have external plugin or dynamic loading I really cant figure either entire page is loaded or not.
document.addEventListener("DOMContentLoaded", function(event) { console.log("DOMContentLoaded with event"); restoreHighlights(ItemInfo); });
window.onload = function () {
restoreHighlights(ItemInfo);
}
window.onload = function() { restoreHighlights(ItemInfo); console.log("onload"); };
window.addEventListener('load', function() { restoreHighlights(ItemInfo); console.log("addEventListener"); });