From 23ca5e32cb4ea38f889a806e65a9919600c141bf Mon Sep 17 00:00:00 2001 From: Davide Cavarretta Date: Thu, 14 Jan 2016 12:47:40 +0100 Subject: [PATCH 1/3] Added Element relative Highlighter Range. Uses a DOM element as doc. This is more failsafe at HTML structure changes. --- demos/highlighter_text_range.html | 156 ++++++++++++++++++++++++++++++ lib/rangy-classapplier.js | 11 +-- lib/rangy-core.js | 29 +++--- lib/rangy-highlighter.js | 22 +++-- lib/rangy-selectionsaverestore.js | 6 +- lib/rangy-serializer.js | 4 +- lib/rangy-textrange.js | 4 +- src/modules/rangy-highlighter.js | 18 +++- 8 files changed, 213 insertions(+), 37 deletions(-) create mode 100644 demos/highlighter_text_range.html diff --git a/demos/highlighter_text_range.html b/demos/highlighter_text_range.html new file mode 100644 index 00000000..87dd0ba7 --- /dev/null +++ b/demos/highlighter_text_range.html @@ -0,0 +1,156 @@ + + + + Rangy Highlighter Module Demo + + + + + + + + + +
+

Highlighter

+

Make a selection in the document and use the buttons below to highlight and unhighlight.

+ + + +
+ + + +

Preserving highlights between page requests

+
+ Highlights can be preserved between page requests. Press the following button to reload the page with the + highlights preserved: +
+ + +
+
+ +
+

Rangy Highlighter Module Demo

+

+ Please use your mouse and/or keyboard to make selections from the sample content below and use the buttons + on the left hand size to create and remove highlights. +

+

+ Association football is a sport played between two teams. It is usually called football, but in + some countries, such as the United States, it is called soccer. In + Japan, New Zealand, South Africa, Australia, Canada and + Republic of Ireland, both words are commonly used. +

+

+ Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are + known as "outfield players." The game is played by kicking a ball into the opponent's goal. A + match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called + half-time. +

+

Competitions

+

+ There are many competitions for football, for both football clubs and countries. Football clubs usually play + other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play + in the English leagues and in the English FA Cup. +

+

Who plays football (this section is in pre-formatted text)

+
+Football is the world's most popular sport. It is played in more
+countries than any other game. In fact, FIFA (the Federation
+Internationale de Football Association) has more members than the
+United Nations.
+
+It is played by both males and females.
+
+
+
+
+ +

+ Text adapted from Simple Wikipedia page on + Association Football, licensed under the + Creative + Commons Attribution/Share-Alike License. +

+ + \ No newline at end of file diff --git a/lib/rangy-classapplier.js b/lib/rangy-classapplier.js index 9fe90d85..b9ab8924 100644 --- a/lib/rangy-classapplier.js +++ b/lib/rangy-classapplier.js @@ -7,10 +7,10 @@ * * Depends on Rangy core. * - * Copyright 2015, Tim Down + * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 20 May 2015 + * Build date: 15 January 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { @@ -698,13 +698,10 @@ // Normalizes nodes after applying a class to a Range. postApply: function(textNodes, range, positionsToPreserve, isUndo) { var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1]; - var merges = [], currentMerge; - var rangeStartNode = firstNode, rangeEndNode = lastNode; var rangeStartOffset = 0, rangeEndOffset = lastNode.length; - - var textNode, precedingTextNode; + var precedingTextNode; // Check for every required merge and create a Merge object for each forEach(textNodes, function(textNode) { @@ -741,7 +738,7 @@ // Apply the merges if (merges.length) { - for (i = 0, len = merges.length; i < len; ++i) { + for (var i = 0, len = merges.length; i < len; ++i) { merges[i].doMerge(positionsToPreserve); } diff --git a/lib/rangy-core.js b/lib/rangy-core.js index dafb1ce1..b08bbf70 100644 --- a/lib/rangy-core.js +++ b/lib/rangy-core.js @@ -2,10 +2,10 @@ * Rangy, a cross-browser JavaScript range and selection library * https://github.com/timdown/rangy * - * Copyright 2015, Tim Down + * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 20 May 2015 + * Build date: 15 January 2016 */ (function(factory, root) { @@ -222,7 +222,7 @@ })(); // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or - // normalization of event properties + // normalization of event properties because we don't need this. var addListener; if (isBrowser) { if (isHostMethod(document, "addEventListener")) { @@ -1303,6 +1303,7 @@ var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); + var getElementAncestor = createAncestorFinder( [1] ); function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { if (getDocTypeNotationEntityAncestor(node, allowSelf)) { @@ -1365,7 +1366,7 @@ var htmlParsingConforms = false; try { styleEl.innerHTML = "x"; - htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node + htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Pre-Blink Opera incorrectly creates an element node } catch (e) { // IE 6 and 7 throw } @@ -1966,6 +1967,12 @@ break; } + assertNoDocTypeNotationEntityAncestor(sc, true); + assertValidOffset(sc, so); + + assertNoDocTypeNotationEntityAncestor(ec, true); + assertValidOffset(ec, eo); + boundaryUpdater(this, sc, so, ec, eo); }, @@ -2128,6 +2135,12 @@ assertNoDocTypeNotationEntityAncestor(node, true); assertValidOffset(node, offset); this.setStartAndEnd(node, offset); + }, + + parentElement: function() { + assertRangeValid(this); + var parentNode = this.commonAncestorContainer; + return parentNode ? getElementAncestor(this.commonAncestorContainer, true) : null; } }); @@ -2149,17 +2162,11 @@ range.endContainer = endContainer; range.endOffset = endOffset; range.document = dom.getDocument(startContainer); - updateCollapsedAndCommonAncestor(range); } function Range(doc) { - this.startContainer = doc; - this.startOffset = 0; - this.endContainer = doc; - this.endOffset = 0; - this.document = doc; - updateCollapsedAndCommonAncestor(this); + updateBoundaries(this, doc, 0, doc, 0); } createPrototypeRange(Range, updateBoundaries); diff --git a/lib/rangy-highlighter.js b/lib/rangy-highlighter.js index bde6ba43..b2a1b276 100644 --- a/lib/rangy-highlighter.js +++ b/lib/rangy-highlighter.js @@ -4,10 +4,10 @@ * * Depends on Rangy core, ClassApplier and optionally TextRange modules. * - * Copyright 2015, Tim Down + * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 20 May 2015 + * Build date: 15 January 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { @@ -34,8 +34,16 @@ return h1.characterRange.start - h2.characterRange.start; } + function getDocumentFromUnknownElement(doc){ + return Object.prototype.toString.call(doc) == "[object HTMLDocument]" ? doc : doc.ownerDocument; + } + + function getContainerElementId(doc){ + return Object.prototype.toString.call(doc) != "[object HTMLDocument]" ? doc.getAttribute('id') : null; + } + function getContainerElement(doc, id) { - return id ? doc.getElementById(id) : getBody(doc); + return id ? getDocumentFromUnknownElement(doc).getElementById(id) : getBody(doc); } /*----------------------------------------------------------------------------------------------------------------*/ @@ -330,7 +338,7 @@ var classApplier = className ? this.classAppliers[className] : null; options = createOptions(options, { - containerElementId: null, + containerElementId: getContainerElementId(this.doc), exclusive: true }); @@ -339,7 +347,7 @@ var containerElement, containerElementRange, containerElementCharRange; if (containerElementId) { - containerElement = this.doc.getElementById(containerElementId); + containerElement = getDocumentFromUnknownElement(this.doc).getElementById(containerElementId); if (containerElement) { containerElementRange = api.createRange(this.doc); containerElementRange.selectNodeContents(containerElement); @@ -431,7 +439,7 @@ var converter = this.converter; options = createOptions(options, { - containerElement: null, + containerElement: this.doc, exclusive: true }); @@ -459,7 +467,7 @@ var classApplier = className ? this.classAppliers[className] : false; options = createOptions(options, { - containerElementId: null, + containerElementId: getContainerElementId(this.doc), exclusive: true }); diff --git a/lib/rangy-selectionsaverestore.js b/lib/rangy-selectionsaverestore.js index b4e35b03..1cd73ce8 100644 --- a/lib/rangy-selectionsaverestore.js +++ b/lib/rangy-selectionsaverestore.js @@ -7,10 +7,10 @@ * * Depends on Rangy core. * - * Copyright 2015, Tim Down + * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 20 May 2015 + * Build date: 15 January 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { @@ -24,7 +24,7 @@ factory(root.rangy); } })(function(rangy) { - rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) { + rangy.createModule("SaveRestore", ["WrappedSelection"], function(api, module) { var dom = api.dom; var removeNode = dom.removeNode; var isDirectionBackward = api.Selection.isDirectionBackward; diff --git a/lib/rangy-serializer.js b/lib/rangy-serializer.js index eec78880..fdd102d2 100644 --- a/lib/rangy-serializer.js +++ b/lib/rangy-serializer.js @@ -8,10 +8,10 @@ * * Depends on Rangy core. * - * Copyright 2015, Tim Down + * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 20 May 2015 + * Build date: 15 January 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { diff --git a/lib/rangy-textrange.js b/lib/rangy-textrange.js index 6e380a12..55898953 100644 --- a/lib/rangy-textrange.js +++ b/lib/rangy-textrange.js @@ -24,10 +24,10 @@ * * Depends on Rangy core. * - * Copyright 2015, Tim Down + * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 20 May 2015 + * Build date: 15 January 2016 */ /** diff --git a/src/modules/rangy-highlighter.js b/src/modules/rangy-highlighter.js index 4527d5d6..9fed2703 100644 --- a/src/modules/rangy-highlighter.js +++ b/src/modules/rangy-highlighter.js @@ -23,8 +23,16 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { return h1.characterRange.start - h2.characterRange.start; } + function getDocumentFromUnknownElement(doc){ + return Object.prototype.toString.call(doc) == "[object HTMLDocument]" ? doc : doc.ownerDocument; + } + + function getContainerElementId(doc){ + return Object.prototype.toString.call(doc) != "[object HTMLDocument]" ? doc.getAttribute('id') : null; + } + function getContainerElement(doc, id) { - return id ? doc.getElementById(id) : getBody(doc); + return id ? getDocumentFromUnknownElement(doc).getElementById(id) : getBody(doc); } /*----------------------------------------------------------------------------------------------------------------*/ @@ -319,7 +327,7 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { var classApplier = className ? this.classAppliers[className] : null; options = createOptions(options, { - containerElementId: null, + containerElementId: getContainerElementId(this.doc), exclusive: true }); @@ -328,7 +336,7 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { var containerElement, containerElementRange, containerElementCharRange; if (containerElementId) { - containerElement = this.doc.getElementById(containerElementId); + containerElement = getDocumentFromUnknownElement(this.doc).getElementById(containerElementId); if (containerElement) { containerElementRange = api.createRange(this.doc); containerElementRange.selectNodeContents(containerElement); @@ -420,7 +428,7 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { var converter = this.converter; options = createOptions(options, { - containerElement: null, + containerElement: this.doc, exclusive: true }); @@ -448,7 +456,7 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { var classApplier = className ? this.classAppliers[className] : false; options = createOptions(options, { - containerElementId: null, + containerElementId: getContainerElementId(this.doc), exclusive: true }); From f19ed459e8cb5ffb636928965ebe2a9c5d73464f Mon Sep 17 00:00:00 2001 From: Davide Cavarretta Date: Tue, 2 Feb 2016 15:43:27 +0100 Subject: [PATCH 2/3] Added Sorting on Deserialize highlights Serialize has highlights sorting but Deserialize, added it. --- lib/rangy-classapplier.js | 2 +- lib/rangy-core.js | 2 +- lib/rangy-highlighter.js | 3 ++- lib/rangy-selectionsaverestore.js | 2 +- lib/rangy-serializer.js | 2 +- lib/rangy-textrange.js | 2 +- src/modules/rangy-highlighter.js | 1 + 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/rangy-classapplier.js b/lib/rangy-classapplier.js index b9ab8924..c1c73738 100644 --- a/lib/rangy-classapplier.js +++ b/lib/rangy-classapplier.js @@ -10,7 +10,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 15 January 2016 + * Build date: 2 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { diff --git a/lib/rangy-core.js b/lib/rangy-core.js index b08bbf70..d9c3ea13 100644 --- a/lib/rangy-core.js +++ b/lib/rangy-core.js @@ -5,7 +5,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 15 January 2016 + * Build date: 2 February 2016 */ (function(factory, root) { diff --git a/lib/rangy-highlighter.js b/lib/rangy-highlighter.js index b2a1b276..d26c15f3 100644 --- a/lib/rangy-highlighter.js +++ b/lib/rangy-highlighter.js @@ -7,7 +7,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 15 January 2016 + * Build date: 2 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { @@ -612,6 +612,7 @@ highlight.apply(); highlights.push(highlight); } + highlights.sort(compareHighlights); this.highlights = highlights; } }; diff --git a/lib/rangy-selectionsaverestore.js b/lib/rangy-selectionsaverestore.js index 1cd73ce8..ad8a0658 100644 --- a/lib/rangy-selectionsaverestore.js +++ b/lib/rangy-selectionsaverestore.js @@ -10,7 +10,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 15 January 2016 + * Build date: 2 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { diff --git a/lib/rangy-serializer.js b/lib/rangy-serializer.js index fdd102d2..0538d5a5 100644 --- a/lib/rangy-serializer.js +++ b/lib/rangy-serializer.js @@ -11,7 +11,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 15 January 2016 + * Build date: 2 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { diff --git a/lib/rangy-textrange.js b/lib/rangy-textrange.js index 55898953..07dfbbc8 100644 --- a/lib/rangy-textrange.js +++ b/lib/rangy-textrange.js @@ -27,7 +27,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 15 January 2016 + * Build date: 2 February 2016 */ /** diff --git a/src/modules/rangy-highlighter.js b/src/modules/rangy-highlighter.js index 9fed2703..69fb8334 100644 --- a/src/modules/rangy-highlighter.js +++ b/src/modules/rangy-highlighter.js @@ -601,6 +601,7 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { highlight.apply(); highlights.push(highlight); } + highlights.sort(compareHighlights); this.highlights = highlights; } }; From 70cfb5f3274beb6936987b229284d9f261f189d2 Mon Sep 17 00:00:00 2001 From: Davide Cavarretta Date: Thu, 4 Feb 2016 10:02:22 +0100 Subject: [PATCH 3/3] Added getHighlightPositionForElement getHighlightPositionForElement it is similar to getHighlightForElement but return the index of the highlights array --- lib/rangy-classapplier.js | 2 +- lib/rangy-core.js | 2 +- lib/rangy-highlighter.js | 12 +++++++++++- lib/rangy-selectionsaverestore.js | 2 +- lib/rangy-serializer.js | 2 +- lib/rangy-textrange.js | 2 +- src/modules/rangy-highlighter.js | 10 ++++++++++ 7 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/rangy-classapplier.js b/lib/rangy-classapplier.js index c1c73738..0afa7eaa 100644 --- a/lib/rangy-classapplier.js +++ b/lib/rangy-classapplier.js @@ -10,7 +10,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 2 February 2016 + * Build date: 4 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { diff --git a/lib/rangy-core.js b/lib/rangy-core.js index d9c3ea13..7c75dbf3 100644 --- a/lib/rangy-core.js +++ b/lib/rangy-core.js @@ -5,7 +5,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 2 February 2016 + * Build date: 4 February 2016 */ (function(factory, root) { diff --git a/lib/rangy-highlighter.js b/lib/rangy-highlighter.js index d26c15f3..cad76ed0 100644 --- a/lib/rangy-highlighter.js +++ b/lib/rangy-highlighter.js @@ -7,7 +7,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 2 February 2016 + * Build date: 4 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { @@ -300,6 +300,16 @@ return null; }, + getHighlightPositionForElement: function(el) { + var highlights = this.highlights; + for (var i = 0, len = highlights.length; i < len; ++i) { + if (highlights[i].containsElement(el)) { + return i; + } + } + return null; + }, + removeHighlights: function(highlights) { for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) { highlight = this.highlights[i]; diff --git a/lib/rangy-selectionsaverestore.js b/lib/rangy-selectionsaverestore.js index ad8a0658..e2b90351 100644 --- a/lib/rangy-selectionsaverestore.js +++ b/lib/rangy-selectionsaverestore.js @@ -10,7 +10,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 2 February 2016 + * Build date: 4 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { diff --git a/lib/rangy-serializer.js b/lib/rangy-serializer.js index 0538d5a5..06ab6e0a 100644 --- a/lib/rangy-serializer.js +++ b/lib/rangy-serializer.js @@ -11,7 +11,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 2 February 2016 + * Build date: 4 February 2016 */ (function(factory, root) { if (typeof define == "function" && define.amd) { diff --git a/lib/rangy-textrange.js b/lib/rangy-textrange.js index 07dfbbc8..5cd89cc6 100644 --- a/lib/rangy-textrange.js +++ b/lib/rangy-textrange.js @@ -27,7 +27,7 @@ * Copyright 2016, Tim Down * Licensed under the MIT license. * Version: 1.3.1-dev - * Build date: 2 February 2016 + * Build date: 4 February 2016 */ /** diff --git a/src/modules/rangy-highlighter.js b/src/modules/rangy-highlighter.js index 69fb8334..b0c0b9df 100644 --- a/src/modules/rangy-highlighter.js +++ b/src/modules/rangy-highlighter.js @@ -289,6 +289,16 @@ rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) { return null; }, + getHighlightPositionForElement: function(el) { + var highlights = this.highlights; + for (var i = 0, len = highlights.length; i < len; ++i) { + if (highlights[i].containsElement(el)) { + return i; + } + } + return null; + }, + removeHighlights: function(highlights) { for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) { highlight = this.highlights[i];