<commit_before>/**
 * Highlighter module for Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core, TextRange and CssClassApplier modules.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Highlighter", function(api, module) {
    api.requireModules( ["CssClassApplier"] );

    var log = log4javascript.getLogger("rangy.Highlighter");
    var dom = api.dom;
    var contains = dom.arrayContains;
    var getBody = dom.getBody;

    // Puts highlights in order, last in document first.
    function compareHighlights(h1, h2) {
        return h1.characterRange.start - h2.characterRange.start;
    }

    var forEach = [].forEach ?
        function(arr, func) {
            arr.forEach(func);
        } :
        function(arr, func) {
            for (var i = 0, len = arr.length; i < len; ++i) {
                func( arr[i] );
            }
        };

    var nextHighlightId = 1;

    /*----------------------------------------------------------------------------------------------------------------*/

    var highlighterTypes = {};

    function HighlighterType(type, converterCreator) {
        this.type = type;
        this.converterCreator = converterCreator
    }

    HighlighterType.prototype.create = function() {
        var converter = this.converterCreator();
        converter.type = this.type;
        return converter;
    };

    function registerHighlighterType(type, converterCreator) {
        highlighterTypes[type] = new HighlighterType(type, converterCreator);
    }

    function getConverter(type) {
        var highlighterType = highlighterTypes[type];
        if (highlighterType instanceof HighlighterType) {
            return highlighterType.create();
        } else {
            throw new Error("Highlighter type '" + type + "' is not valid")
        }
    }

    api.registerHighlighterType = registerHighlighterType;

    /*----------------------------------------------------------------------------------------------------------------*/

    function CharacterRange(start, end) {
        this.start = start;
        this.end = end;
    }

    CharacterRange.prototype = {
        intersects: function(charRange) {
            return this.start < charRange.end && this.end > charRange.start;
        },

        union: function(charRange) {
            return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
        },

        intersection: function(charRange) {
            return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
        },

        toString: function() {
            return "[CharacterRange(" + this.start + ", " + this.end + ")]";
        }
    };

    CharacterRange.fromCharacterRange = function(charRange) {
        return new CharacterRange(charRange.start, charRange.end);
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    var textContentConverter = {
        rangeToCharacterRange: function(range, containerNode) {
            var bookmark = range.getBookmark(containerNode);
            return new CharacterRange(bookmark.start, bookmark.end);
        },

        characterRangeToRange: function(doc, characterRange, containerNode) {
            var range = api.createRange(doc);
            range.moveToBookmark({
                start: characterRange.start,
                end: characterRange.end,
                containerNode: containerNode
            });

            return range;
        },

        serializeSelection: function(selection, containerNode) {
            var ranges = selection.getAllRanges(), rangeCount = ranges.length;
            var rangeInfos = [];

            var backward = rangeCount == 1 && selection.isBackward();

            for (var i = 0, len = ranges.length; i < len; ++i) {
                rangeInfos[i] = {
                    characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
                    backward: backward
                };
            }

            return rangeInfos;
        },

        restoreSelection: function(selection, savedSelection, containerNode) {
            selection.removeAllRanges();
            var doc = selection.win.document;
            for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
                rangeInfo = savedSelection[i];
                characterRange = rangeInfo.characterRange;
                range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
                selection.addRange(range, rangeInfo.backward);
            }
        }
    };

    registerHighlighterType("textContent", function() {
        return textContentConverter;
    });

    /*----------------------------------------------------------------------------------------------------------------*/

    // Lazily load the TextRange-based converter so that the dependency is only checked when required.
    registerHighlighterType("TextRange", (function() {
        var converter;

        return function() {
            if (!converter) {
                // Test that textRangeModule exists and is supported
                var textRangeModule = api.modules.TextRange;
                if (!textRangeModule) {
                    throw new Error("TextRange module is missing.");
                } else if (!textRangeModule.supported) {
                    throw new Error("TextRange module is present but not supported.");
                }

                converter = {
                    rangeToCharacterRange: function(range, containerNode) {
                        return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
                    },

                    characterRangeToRange: function(doc, characterRange, containerNode) {
                        var range = api.createRange(doc);
                        range.selectCharacters(containerNode, characterRange.start, characterRange.end);
                        return range;
                    },

                    serializeSelection: function(selection, containerNode) {
                        return selection.saveCharacterRanges(containerNode);
                    },

                    restoreSelection: function(selection, savedSelection, containerNode) {
                        selection.restoreCharacterRanges(containerNode, savedSelection);
                    }
                }
            }

            return converter;
        }
    })());

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
        if (id) {
            this.id = id;
            nextHighlightId = Math.max(nextHighlightId, id + 1);
        } else {
            this.id = nextHighlightId++;
        }
        this.characterRange = characterRange;
        this.doc = doc;
        this.classApplier = classApplier;
        this.converter = converter;
        this.containerElementId = containerElementId || null;
        this.applied = false;
    }

    Highlight.prototype = {
        getContainerElement: function() {
            return this.containerElementId ? this.doc.getElementById(this.containerElementId) : getBody(this.doc);
        },

        getRange: function() {
            return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
        },

        fromRange: function(range) {
            this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
        },

        getText: function() {
            return this.getRange().toString();
        },

        containsElement: function(el) {
            return this.getRange().containsNodeContents(el.firstChild);
        },

        unapply: function() {
            this.classApplier.undoToRange(this.getRange());
            this.applied = false;
        },

        apply: function() {
            this.classApplier.applyToRange(this.getRange());
            this.applied = true;
        },

        getHighlightElements: function() {
            return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
        },

        toString: function() {
            return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
                this.characterRange.start + " - " + this.characterRange.end + ")]";
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlighter(doc, type) {
        type = type || "textContent";
        this.doc = doc || document;
        this.classAppliers = {};
        this.highlights = [];
        this.converter = getConverter(type);
    }

    Highlighter.prototype = {
        addClassApplier: function(classApplier) {
            this.classAppliers[classApplier.cssClass] = classApplier;
        },

        getHighlightForElement: function(el) {
            var highlights = this.highlights;
            for (var i = 0, len = highlights.length; i < len; ++i) {
                if (highlights[i].containsElement(el)) {
                    return highlights[i];
                }
            }
            return null;
        },

        removeHighlights: function(highlights) {
            for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
                highlight = this.highlights[i];
                if (contains(highlights, highlight)) {
                    highlight.unapply();
                    this.highlights.splice(i--, 1);
                }
            }
        },

        removeAllHighlights: function() {
            this.removeHighlights(this.highlights);
        },

        getIntersectingHighlights: function(ranges) {
            // Test each range against each of the highlighted ranges to see whether they overlap
            var intersectingHighlights = [], highlights = this.highlights, converter = this.converter;
            forEach(ranges, function(range) {
                //var selCharRange = converter.rangeToCharacterRange(range);
                forEach(highlights, function(highlight) {
                    if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
                        intersectingHighlights.push(highlight);
                    }
                });
            });

            return intersectingHighlights;
        },

        highlightCharacterRanges: function(className, charRanges, containerElementId) {
            var i, len, j;
            var highlights = this.highlights;
            var converter = this.converter;
            var doc = this.doc;
            var highlightsToRemove = [];
            var classApplier = this.classAppliers[className];
            containerElementId = containerElementId || null;

            var containerElement, containerElementRange, containerElementCharRange;
            if (containerElementId) {
                containerElement = this.doc.getElementById(containerElementId);
                if (containerElement) {
                    containerElementRange = api.createRange(this.doc);
                    containerElementRange.selectNodeContents(containerElement);
                    containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
                    containerElementRange.detach();
                }
            }

            var charRange, highlightCharRange, highlightRange, merged;
            for (i = 0, len = charRanges.length; i < len; ++i) {
                charRange = charRanges[i];
                merged = false;

                // Restrict character range to container element, if it exists
                if (containerElementCharRange) {
                    charRange = charRange.intersection(containerElementCharRange);
                }

                // Check for intersection with existing highlights. For each intersection, create a new highlight
                // which is the union of the highlight range and the selected range
                for (j = 0; j < highlights.length; ++j) {
                    if (containerElementId == highlights[j].containerElementId) {
                        highlightCharRange = highlights[j].characterRange;

                        if (highlightCharRange.intersects(charRange)) {
                            // Replace the existing highlight in the list of current highlights and add it to the list for
                            // removal
                            highlightsToRemove.push(highlights[j]);
                            highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
                        }
                    }
                }

                if (!merged) {
                    highlights.push( new Highlight(doc, charRange, classApplier, converter, null, containerElementId) );
                }
            }

            // Remove the old highlights
            forEach(highlightsToRemove, function(highlightToRemove) {
                highlightToRemove.unapply();
            });

            // Apply new highlights
            var newHighlights = [];
            forEach(highlights, function(highlight) {
                if (!highlight.applied) {
                    highlight.apply();
                    newHighlights.push(highlight);
                }
            });

            return newHighlights;
        },

        highlightRanges: function(className, ranges, containerElement) {
            var selCharRanges = [];
            var converter = this.converter;
            var containerElementId = containerElement ? containerElement.id : null;
            var containerElementRange;
            if (containerElement) {
                containerElementRange = api.createRange(containerElement);
                containerElementRange.selectNodeContents(containerElement);
            }

            forEach(ranges, function(range) {
                var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
                selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
            });

            return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
        },

        highlightSelection: function(className, selection, containerElementId) {
            var converter = this.converter;
            selection = selection || api.getSelection();
            var classApplier = this.classAppliers[className];
            var highlights = this.highlights;
            var doc = selection.win.document;
            var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);

            if (!classApplier) {
                throw new Error("No class applier found for class '" + className + "'");
            }

            // Store the existing selection as character ranges
            var serializedSelection = converter.serializeSelection(selection, containerElement);

            // Create an array of selected character ranges
            var selCharRanges = [];
            forEach(serializedSelection, function(rangeInfo) {
                selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
            });

            var newHighlights = this.highlightCharacterRanges(className, selCharRanges, containerElementId);

            // Restore selection
            converter.restoreSelection(selection, serializedSelection, containerElement);

            return newHighlights;
        },

        unhighlightSelection: function(selection) {
            selection = selection || api.getSelection();
            var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
            this.removeHighlights(intersectingHighlights);
            selection.removeAllRanges();
        },

        selectionOverlapsHighlight: function(selection) {
            selection = selection || api.getSelection();
            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
        },

        serialize: function(options) {
            var highlights = this.highlights;
            highlights.sort(compareHighlights);
            var serializedHighlights = ["type:" + this.converter.type];

            forEach(highlights, function(highlight) {
                var characterRange = highlight.characterRange;
                var parts = [
                    characterRange.start,
                    characterRange.end,
                    highlight.id,
                    highlight.classApplier.cssClass,
                    highlight.containerElementId
                ];
                if (options && options.serializeHighlightText) {
                    parts.push(highlight.getText());
                }
                serializedHighlights.push( parts.join("$") );
            });

            return serializedHighlights.join("|");
        },

        deserialize: function(serialized) {
            var serializedHighlights = serialized.split("|");
            var highlights = [];

            var firstHighlight = serializedHighlights[0];
            var regexResult;
            var serializationType, serializationConverter, convertType = false;
            if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
                serializationType = regexResult[1];
                if (serializationType != this.converter.type) {
                    serializationConverter = getConverter(serializationType);
                    convertType = true;
                }
                serializedHighlights.shift();
            } else {
                throw new Error("Serialized highlights are invalid.");
            }

            var classApplier, highlight, characterRange, containerElementId, containerElement;

            for (var i = serializedHighlights.length, parts; i-- > 0; ) {
                parts = serializedHighlights[i].split("$");
                characterRange = new CharacterRange(+parts[0], +parts[1]);
                containerElementId = parts[4] || null;
                containerElement = containerElementId ? this.doc.getElementById(containerElementId) : getBody(this.doc);

                // Convert to the current Highlighter's type, if different from the serialization type
                if (convertType) {
                    characterRange = this.converter.rangeToCharacterRange(
                        serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
                        containerElement
                    );
                }

                classApplier = this.classAppliers[parts[3]];
                highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parts[2], containerElementId);
                highlight.apply();
                highlights.push(highlight);
            }
            this.highlights = highlights;
        }
    };

    api.Highlighter = Highlighter;

    api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
        return new Highlighter(doc, rangeCharacterOffsetConverterType);
    };
});
<commit_msg>Fix for highlight IDs<commit_after>/**
 * Highlighter module for Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core, TextRange and CssClassApplier modules.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Highlighter", function(api, module) {
    api.requireModules( ["CssClassApplier"] );

    var log = log4javascript.getLogger("rangy.Highlighter");
    var dom = api.dom;
    var contains = dom.arrayContains;
    var getBody = dom.getBody;

    // Puts highlights in order, last in document first.
    function compareHighlights(h1, h2) {
        return h1.characterRange.start - h2.characterRange.start;
    }

    var forEach = [].forEach ?
        function(arr, func) {
            arr.forEach(func);
        } :
        function(arr, func) {
            for (var i = 0, len = arr.length; i < len; ++i) {
                func( arr[i] );
            }
        };

    var nextHighlightId = 1;

    /*----------------------------------------------------------------------------------------------------------------*/

    var highlighterTypes = {};

    function HighlighterType(type, converterCreator) {
        this.type = type;
        this.converterCreator = converterCreator
    }

    HighlighterType.prototype.create = function() {
        var converter = this.converterCreator();
        converter.type = this.type;
        return converter;
    };

    function registerHighlighterType(type, converterCreator) {
        highlighterTypes[type] = new HighlighterType(type, converterCreator);
    }

    function getConverter(type) {
        var highlighterType = highlighterTypes[type];
        if (highlighterType instanceof HighlighterType) {
            return highlighterType.create();
        } else {
            throw new Error("Highlighter type '" + type + "' is not valid")
        }
    }

    api.registerHighlighterType = registerHighlighterType;

    /*----------------------------------------------------------------------------------------------------------------*/

    function CharacterRange(start, end) {
        this.start = start;
        this.end = end;
    }

    CharacterRange.prototype = {
        intersects: function(charRange) {
            return this.start < charRange.end && this.end > charRange.start;
        },

        union: function(charRange) {
            return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
        },

        intersection: function(charRange) {
            return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
        },

        toString: function() {
            return "[CharacterRange(" + this.start + ", " + this.end + ")]";
        }
    };

    CharacterRange.fromCharacterRange = function(charRange) {
        return new CharacterRange(charRange.start, charRange.end);
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    var textContentConverter = {
        rangeToCharacterRange: function(range, containerNode) {
            var bookmark = range.getBookmark(containerNode);
            return new CharacterRange(bookmark.start, bookmark.end);
        },

        characterRangeToRange: function(doc, characterRange, containerNode) {
            var range = api.createRange(doc);
            range.moveToBookmark({
                start: characterRange.start,
                end: characterRange.end,
                containerNode: containerNode
            });

            return range;
        },

        serializeSelection: function(selection, containerNode) {
            var ranges = selection.getAllRanges(), rangeCount = ranges.length;
            var rangeInfos = [];

            var backward = rangeCount == 1 && selection.isBackward();

            for (var i = 0, len = ranges.length; i < len; ++i) {
                rangeInfos[i] = {
                    characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
                    backward: backward
                };
            }

            return rangeInfos;
        },

        restoreSelection: function(selection, savedSelection, containerNode) {
            selection.removeAllRanges();
            var doc = selection.win.document;
            for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
                rangeInfo = savedSelection[i];
                characterRange = rangeInfo.characterRange;
                range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
                selection.addRange(range, rangeInfo.backward);
            }
        }
    };

    registerHighlighterType("textContent", function() {
        return textContentConverter;
    });

    /*----------------------------------------------------------------------------------------------------------------*/

    // Lazily load the TextRange-based converter so that the dependency is only checked when required.
    registerHighlighterType("TextRange", (function() {
        var converter;

        return function() {
            if (!converter) {
                // Test that textRangeModule exists and is supported
                var textRangeModule = api.modules.TextRange;
                if (!textRangeModule) {
                    throw new Error("TextRange module is missing.");
                } else if (!textRangeModule.supported) {
                    throw new Error("TextRange module is present but not supported.");
                }

                converter = {
                    rangeToCharacterRange: function(range, containerNode) {
                        return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
                    },

                    characterRangeToRange: function(doc, characterRange, containerNode) {
                        var range = api.createRange(doc);
                        range.selectCharacters(containerNode, characterRange.start, characterRange.end);
                        return range;
                    },

                    serializeSelection: function(selection, containerNode) {
                        return selection.saveCharacterRanges(containerNode);
                    },

                    restoreSelection: function(selection, savedSelection, containerNode) {
                        selection.restoreCharacterRanges(containerNode, savedSelection);
                    }
                }
            }

            return converter;
        }
    })());

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
        if (id) {
            this.id = id;
            nextHighlightId = Math.max(nextHighlightId, id + 1);
        } else {
            this.id = nextHighlightId++;
        }
        this.characterRange = characterRange;
        this.doc = doc;
        this.classApplier = classApplier;
        this.converter = converter;
        this.containerElementId = containerElementId || null;
        this.applied = false;
    }

    Highlight.prototype = {
        getContainerElement: function() {
            return this.containerElementId ? this.doc.getElementById(this.containerElementId) : getBody(this.doc);
        },

        getRange: function() {
            return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
        },

        fromRange: function(range) {
            this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
        },

        getText: function() {
            return this.getRange().toString();
        },

        containsElement: function(el) {
            return this.getRange().containsNodeContents(el.firstChild);
        },

        unapply: function() {
            this.classApplier.undoToRange(this.getRange());
            this.applied = false;
        },

        apply: function() {
            this.classApplier.applyToRange(this.getRange());
            this.applied = true;
        },

        getHighlightElements: function() {
            return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
        },

        toString: function() {
            return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
                this.characterRange.start + " - " + this.characterRange.end + ")]";
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlighter(doc, type) {
        type = type || "textContent";
        this.doc = doc || document;
        this.classAppliers = {};
        this.highlights = [];
        this.converter = getConverter(type);
    }

    Highlighter.prototype = {
        addClassApplier: function(classApplier) {
            this.classAppliers[classApplier.cssClass] = classApplier;
        },

        getHighlightForElement: function(el) {
            var highlights = this.highlights;
            for (var i = 0, len = highlights.length; i < len; ++i) {
                if (highlights[i].containsElement(el)) {
                    return highlights[i];
                }
            }
            return null;
        },

        removeHighlights: function(highlights) {
            for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
                highlight = this.highlights[i];
                if (contains(highlights, highlight)) {
                    highlight.unapply();
                    this.highlights.splice(i--, 1);
                }
            }
        },

        removeAllHighlights: function() {
            this.removeHighlights(this.highlights);
        },

        getIntersectingHighlights: function(ranges) {
            // Test each range against each of the highlighted ranges to see whether they overlap
            var intersectingHighlights = [], highlights = this.highlights, converter = this.converter;
            forEach(ranges, function(range) {
                //var selCharRange = converter.rangeToCharacterRange(range);
                forEach(highlights, function(highlight) {
                    if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
                        intersectingHighlights.push(highlight);
                    }
                });
            });

            return intersectingHighlights;
        },

        highlightCharacterRanges: function(className, charRanges, containerElementId) {
            var i, len, j;
            var highlights = this.highlights;
            var converter = this.converter;
            var doc = this.doc;
            var highlightsToRemove = [];
            var classApplier = this.classAppliers[className];
            containerElementId = containerElementId || null;

            var containerElement, containerElementRange, containerElementCharRange;
            if (containerElementId) {
                containerElement = this.doc.getElementById(containerElementId);
                if (containerElement) {
                    containerElementRange = api.createRange(this.doc);
                    containerElementRange.selectNodeContents(containerElement);
                    containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
                    containerElementRange.detach();
                }
            }

            var charRange, highlightCharRange, highlightRange, merged;
            for (i = 0, len = charRanges.length; i < len; ++i) {
                charRange = charRanges[i];
                merged = false;

                // Restrict character range to container element, if it exists
                if (containerElementCharRange) {
                    charRange = charRange.intersection(containerElementCharRange);
                }

                // Check for intersection with existing highlights. For each intersection, create a new highlight
                // which is the union of the highlight range and the selected range
                for (j = 0; j < highlights.length; ++j) {
                    if (containerElementId == highlights[j].containerElementId) {
                        highlightCharRange = highlights[j].characterRange;

                        if (highlightCharRange.intersects(charRange)) {
                            // Replace the existing highlight in the list of current highlights and add it to the list for
                            // removal
                            highlightsToRemove.push(highlights[j]);
                            highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
                        }
                    }
                }

                if (!merged) {
                    highlights.push( new Highlight(doc, charRange, classApplier, converter, null, containerElementId) );
                }
            }

            // Remove the old highlights
            forEach(highlightsToRemove, function(highlightToRemove) {
                highlightToRemove.unapply();
            });

            // Apply new highlights
            var newHighlights = [];
            forEach(highlights, function(highlight) {
                if (!highlight.applied) {
                    highlight.apply();
                    newHighlights.push(highlight);
                }
            });

            return newHighlights;
        },

        highlightRanges: function(className, ranges, containerElement) {
            var selCharRanges = [];
            var converter = this.converter;
            var containerElementId = containerElement ? containerElement.id : null;
            var containerElementRange;
            if (containerElement) {
                containerElementRange = api.createRange(containerElement);
                containerElementRange.selectNodeContents(containerElement);
            }

            forEach(ranges, function(range) {
                var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
                selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
            });

            return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
        },

        highlightSelection: function(className, selection, containerElementId) {
            var converter = this.converter;
            selection = selection || api.getSelection();
            var classApplier = this.classAppliers[className];
            var highlights = this.highlights;
            var doc = selection.win.document;
            var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);

            if (!classApplier) {
                throw new Error("No class applier found for class '" + className + "'");
            }

            // Store the existing selection as character ranges
            var serializedSelection = converter.serializeSelection(selection, containerElement);

            // Create an array of selected character ranges
            var selCharRanges = [];
            forEach(serializedSelection, function(rangeInfo) {
                selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
            });

            var newHighlights = this.highlightCharacterRanges(className, selCharRanges, containerElementId);

            // Restore selection
            converter.restoreSelection(selection, serializedSelection, containerElement);

            return newHighlights;
        },

        unhighlightSelection: function(selection) {
            selection = selection || api.getSelection();
            var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
            this.removeHighlights(intersectingHighlights);
            selection.removeAllRanges();
        },

        selectionOverlapsHighlight: function(selection) {
            selection = selection || api.getSelection();
            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
        },

        serialize: function(options) {
            var highlights = this.highlights;
            highlights.sort(compareHighlights);
            var serializedHighlights = ["type:" + this.converter.type];

            forEach(highlights, function(highlight) {
                var characterRange = highlight.characterRange;
                var parts = [
                    characterRange.start,
                    characterRange.end,
                    highlight.id,
                    highlight.classApplier.cssClass,
                    highlight.containerElementId
                ];
                if (options && options.serializeHighlightText) {
                    parts.push(highlight.getText());
                }
                serializedHighlights.push( parts.join("$") );
            });

            return serializedHighlights.join("|");
        },

        deserialize: function(serialized) {
            var serializedHighlights = serialized.split("|");
            var highlights = [];

            var firstHighlight = serializedHighlights[0];
            var regexResult;
            var serializationType, serializationConverter, convertType = false;
            if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
                serializationType = regexResult[1];
                if (serializationType != this.converter.type) {
                    serializationConverter = getConverter(serializationType);
                    convertType = true;
                }
                serializedHighlights.shift();
            } else {
                throw new Error("Serialized highlights are invalid.");
            }

            var classApplier, highlight, characterRange, containerElementId, containerElement;

            for (var i = serializedHighlights.length, parts; i-- > 0; ) {
                parts = serializedHighlights[i].split("$");
                characterRange = new CharacterRange(+parts[0], +parts[1]);
                containerElementId = parts[4] || null;
                containerElement = containerElementId ? this.doc.getElementById(containerElementId) : getBody(this.doc);

                // Convert to the current Highlighter's type, if different from the serialization type
                if (convertType) {
                    characterRange = this.converter.rangeToCharacterRange(
                        serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
                        containerElement
                    );
                }

                classApplier = this.classAppliers[parts[3]];
                highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parseInt(parts[2]), containerElementId);
                highlight.apply();
                highlights.push(highlight);
            }
            this.highlights = highlights;
        }
    };

    api.Highlighter = Highlighter;

    api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
        return new Highlighter(doc, rangeCharacterOffsetConverterType);
    };
});
<|endoftext|>


<commit_before>var Markdown = Ember.Component.extend({
    adjustScrollPosition: function () {
        var scrollWrapper = this.$('.entry-preview-content').get(0),
        // calculate absolute scroll position from percentage
            scrollPixel = scrollWrapper.scrollHeight * this.get('scrollPosition');

        scrollWrapper.scrollTop = scrollPixel; // adjust scroll position
    }.observes('scrollPosition')
});

export default Markdown;<commit_msg>fix mirrored scrolling in markdown editor<commit_after>var Markdown = Ember.Component.extend({
    adjustScrollPosition: function () {
        var scrollWrapper = this.$().closest('.entry-preview-content').get(0),
        // calculate absolute scroll position from percentage
            scrollPixel = scrollWrapper.scrollHeight * this.get('scrollPosition');

        scrollWrapper.scrollTop = scrollPixel; // adjust scroll position
    }.observes('scrollPosition')
});

export default Markdown;<|endoftext|>


<commit_before>/**
 * Highlighter module for Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core, TextRange and CssClassApplier modules.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Highlighter", function(api, module) {
    api.requireModules( ["CssClassApplier"] );

    var log = log4javascript.getLogger("rangy.Highlighter");
    var dom = api.dom;
    var contains = dom.arrayContains;
    var getBody = dom.getBody;

    // Puts highlights in order, last in document first.
    function compareHighlights(h1, h2) {
        return h1.characterRange.start - h2.characterRange.start;
    }

    var forEach = [].forEach ?
        function(arr, func) {
            arr.forEach(func);
        } :
        function(arr, func) {
            for (var i = 0, len = arr.length; i < len; ++i) {
                func( arr[i] );
            }
        };

    var nextHighlightId = 1;

    /*----------------------------------------------------------------------------------------------------------------*/

    var highlighterTypes = {};

    function HighlighterType(type, converterCreator) {
        this.type = type;
        this.converterCreator = converterCreator
    }

    HighlighterType.prototype.create = function() {
        var converter = this.converterCreator();
        converter.type = this.type;
        return converter;
    };

    function registerHighlighterType(type, converterCreator) {
        highlighterTypes[type] = new HighlighterType(type, converterCreator);
    }

    function getConverter(type) {
        var highlighterType = highlighterTypes[type];
        if (highlighterType instanceof HighlighterType) {
            return highlighterType.create();
        } else {
            throw new Error("Highlighter type '" + type + "' is not valid")
        }
    }

    api.registerHighlighterType = registerHighlighterType;

    /*----------------------------------------------------------------------------------------------------------------*/

    function CharacterRange(start, end) {
        this.start = start;
        this.end = end;
    }

    CharacterRange.prototype = {
        intersects: function(charRange) {
            return this.start < charRange.end && this.end > charRange.start;
        },

        union: function(charRange) {
            return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
        },

        intersection: function(charRange) {
            return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
        },

        toString: function() {
            return "[CharacterRange(" + this.start + ", " + this.end + ")]";
        }
    };

    CharacterRange.fromCharacterRange = function(charRange) {
        return new CharacterRange(charRange.start, charRange.end);
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    var textContentConverter = {
        rangeToCharacterRange: function(range, containerNode) {
            var bookmark = range.getBookmark(containerNode);
            return new CharacterRange(bookmark.start, bookmark.end);
        },

        characterRangeToRange: function(doc, characterRange, containerNode) {
            var range = api.createRange(doc);
            range.moveToBookmark({
                start: characterRange.start,
                end: characterRange.end,
                containerNode: containerNode
            });

            return range;
        },

        serializeSelection: function(selection, containerNode) {
            var ranges = selection.getAllRanges(), rangeCount = ranges.length;
            var rangeInfos = [];

            var backward = rangeCount == 1 && selection.isBackward();

            for (var i = 0, len = ranges.length; i < len; ++i) {
                rangeInfos[i] = {
                    characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
                    backward: backward
                };
            }

            return rangeInfos;
        },

        restoreSelection: function(selection, savedSelection, containerNode) {
            selection.removeAllRanges();
            var doc = selection.win.document;
            for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
                rangeInfo = savedSelection[i];
                characterRange = rangeInfo.characterRange;
                range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
                selection.addRange(range, rangeInfo.backward);
            }
        }
    };

    registerHighlighterType("textContent", function() {
        return textContentConverter;
    });

    /*----------------------------------------------------------------------------------------------------------------*/

    // Lazily load the TextRange-based converter so that the dependency is only checked when required.
    registerHighlighterType("TextRange", (function() {
        var converter;

        return function() {
            if (!converter) {
                // Test that textRangeModule exists and is supported
                var textRangeModule = api.modules.TextRange;
                if (!textRangeModule) {
                    throw new Error("TextRange module is missing.");
                } else if (!textRangeModule.supported) {
                    throw new Error("TextRange module is present but not supported.");
                }

                converter = {
                    rangeToCharacterRange: function(range, containerNode) {
                        return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
                    },

                    characterRangeToRange: function(doc, characterRange, containerNode) {
                        var range = api.createRange(doc);
                        range.selectCharacters(containerNode, characterRange.start, characterRange.end);
                        return range;
                    },

                    serializeSelection: function(selection, containerNode) {
                        return selection.saveCharacterRanges(containerNode);
                    },

                    restoreSelection: function(selection, savedSelection, containerNode) {
                        selection.restoreCharacterRanges(containerNode, savedSelection);
                    }
                }
            }

            return converter;
        }
    })());

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
        if (id) {
            this.id = id;
            nextHighlightId = Math.max(nextHighlightId, id + 1);
        } else {
            this.id = nextHighlightId++;
        }
        this.characterRange = characterRange;
        this.doc = doc;
        this.classApplier = classApplier;
        this.converter = converter;
        this.containerElementId = containerElementId || null;
        this.applied = false;
    }

    Highlight.prototype = {
        getContainerElement: function() {
            return this.containerElementId ? this.doc.getElementById(this.containerElementId) : getBody(this.doc);
        },

        getRange: function() {
            return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
        },

        fromRange: function(range) {
            this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
        },

        containsElement: function(el) {
            return this.getRange().containsNodeContents(el.firstChild);
        },

        unapply: function() {
            this.classApplier.undoToRange(this.getRange());
            this.applied = false;
        },

        apply: function() {
            this.classApplier.applyToRange(this.getRange());
            this.applied = true;
        },

        getHighlightElements: function() {
            return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
        },

        toString: function() {
            return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
                this.characterRange.start + " - " + this.characterRange.end + ")]";
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlighter(doc, type) {
        type = type || "textContent";
        this.doc = doc || document;
        this.classAppliers = {};
        this.highlights = [];
        this.converter = getConverter(type);
    }

    Highlighter.prototype = {
        addClassApplier: function(classApplier) {
            this.classAppliers[classApplier.cssClass] = classApplier;
        },

        getHighlightForElement: function(el) {
            var highlights = this.highlights;
            for (var i = 0, len = highlights.length; i < len; ++i) {
                if (highlights[i].containsElement(el)) {
                    return highlights[i];
                }
            }
            return null;
        },

        removeHighlights: function(highlights) {
            for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
                highlight = this.highlights[i];
                if (contains(highlights, highlight)) {
                    highlight.unapply();
                    this.highlights.splice(i--, 1);
                }
            }
        },

        removeAllHighlights: function() {
            this.removeHighlights(this.highlights);
        },

        getIntersectingHighlights: function(ranges) {
            // Test each range against each of the highlighted ranges to see whether they overlap
            var intersectingHighlights = [], highlights = this.highlights, converter = this.converter;
            forEach(ranges, function(range) {
                //var selCharRange = converter.rangeToCharacterRange(range);
                forEach(highlights, function(highlight) {
                    if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
                        intersectingHighlights.push(highlight);
                    }
                });
            });

            return intersectingHighlights;
        },

        highlightCharacterRanges: function(className, charRanges, containerElementId) {
            var i, len, j;
            var highlights = this.highlights;
            var converter = this.converter;
            var doc = this.doc;
            var highlightsToRemove = [];
            var classApplier = this.classAppliers[className];
            containerElementId = containerElementId || null;

            var containerElement, containerElementRange, containerElementCharRange;
            if (containerElementId) {
                containerElement = this.doc.getElementById(containerElementId);
                if (containerElement) {
                    containerElementRange = api.createRange(this.doc);
                    containerElementRange.selectNodeContents(containerElement);
                    containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
                    containerElementRange.detach();
                }
            }

            var charRange, highlightCharRange, highlightRange, merged;
            for (i = 0, len = charRanges.length; i < len; ++i) {
                charRange = charRanges[i];
                merged = false;

                // Restrict character range to container element, if it exists
                if (containerElementCharRange) {
                    charRange = charRange.intersection(containerElementCharRange);
                }

                // Check for intersection with existing highlights. For each intersection, create a new highlight
                // which is the union of the highlight range and the selected range
                for (j = 0; j < highlights.length; ++j) {
                    if (containerElementId == highlights[j].containerElementId) {
                        highlightCharRange = highlights[j].characterRange;

                        if (highlightCharRange.intersects(charRange)) {
                            // Replace the existing highlight in the list of current highlights and add it to the list for
                            // removal
                            highlightsToRemove.push(highlights[j]);
                            highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
                        }
                    }
                }

                if (!merged) {
                    highlights.push( new Highlight(doc, charRange, classApplier, converter, null, containerElementId) );
                }
            }

            // Remove the old highlights
            forEach(highlightsToRemove, function(highlightToRemove) {
                highlightToRemove.unapply();
            });

            // Apply new highlights
            var newHighlights = [];
            forEach(highlights, function(highlight) {
                if (!highlight.applied) {
                    highlight.apply();
                    newHighlights.push(highlight);
                }
            });

            return newHighlights;
        },

        highlightRanges: function(className, ranges, containerElement) {
            var selCharRanges = [];
            var converter = this.converter;
            var containerElementId = containerElement ? containerElement.id : null;
            var containerElementRange;
            if (containerElement) {
                containerElementRange = api.createRange(containerElement);
                containerElementRange.selectNodeContents(containerElement);
            }

            forEach(ranges, function(range) {
                var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
                selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
            });

            return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
        },

        highlightSelection: function(className, selection, containerElementId) {
            var converter = this.converter;
            selection = selection || api.getSelection();
            var classApplier = this.classAppliers[className];
            var highlights = this.highlights;
            var doc = selection.win.document;
            var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);

            if (!classApplier) {
                throw new Error("No class applier found for class '" + className + "'");
            }

            // Store the existing selection as character ranges
            var serializedSelection = converter.serializeSelection(selection, containerElement);

            // Create an array of selected character ranges
            var selCharRanges = [];
            forEach(serializedSelection, function(rangeInfo) {
                selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
            });

            var newHighlights = this.highlightCharacterRanges(className, selCharRanges, containerElementId);

            // Restore selection
            converter.restoreSelection(selection, serializedSelection, containerElement);

            return newHighlights;
        },

        unhighlightSelection: function(selection) {
            selection = selection || api.getSelection();
            var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
            this.removeHighlights(intersectingHighlights);
            selection.removeAllRanges();
        },

        selectionOverlapsHighlight: function(selection) {
            selection = selection || api.getSelection();
            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
        },

        serialize: function() {
            var highlights = this.highlights;
            highlights.sort(compareHighlights);
            var serializedHighlights = ["type:" + this.converter.type];

            forEach(highlights, function(highlight) {
                var characterRange = highlight.characterRange;
                serializedHighlights.push( [
                    characterRange.start,
                    characterRange.end,
                    highlight.id,
                    highlight.classApplier.cssClass,
                    highlight.containerElementId
                ].join("$") );
            });

            return serializedHighlights.join("|");
        },

        deserialize: function(serialized) {
            var serializedHighlights = serialized.split("|");
            var highlights = [];

            var firstHighlight = serializedHighlights[0];
            var regexResult;
            var serializationType, serializationConverter, convertType = false;
            if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
                serializationType = regexResult[1];
                if (serializationType != this.converter.type) {
                    serializationConverter = getConverter(serializationType);
                    convertType = true;
                }
                serializedHighlights.shift();
            } else {
                throw new Error("Serialized highlights are invalid.");
            }

            var classApplier, highlight, characterRange, containerElementId, containerElement;

            for (var i = serializedHighlights.length, parts; i-- > 0; ) {
                parts = serializedHighlights[i].split("$");
                characterRange = new CharacterRange(+parts[0], +parts[1]);
                containerElementId = parts[4] || null;
                containerElement = containerElementId ? this.doc.getElementById(containerElementId) : getBody(this.doc);

                // Convert to the current Highlighter's type, if different from the serialization type
                if (convertType) {
                    characterRange = this.converter.rangeToCharacterRange(
                        serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
                        containerElement
                    );
                }

                classApplier = this.classAppliers[parts[3]];
                highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parts[2], containerElementId);
                highlight.apply();
                highlights.push(highlight);
            }
            this.highlights = highlights;
        }
    };

    api.Highlighter = Highlighter;

    api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
        return new Highlighter(doc, rangeCharacterOffsetConverterType);
    };
});
<commit_msg>Added option to serialize highlight text<commit_after>/**
 * Highlighter module for Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Depends on Rangy core, TextRange and CssClassApplier modules.
 *
 * Copyright %%build:year%%, Tim Down
 * Licensed under the MIT license.
 * Version: %%build:version%%
 * Build date: %%build:date%%
 */
rangy.createModule("Highlighter", function(api, module) {
    api.requireModules( ["CssClassApplier"] );

    var log = log4javascript.getLogger("rangy.Highlighter");
    var dom = api.dom;
    var contains = dom.arrayContains;
    var getBody = dom.getBody;

    // Puts highlights in order, last in document first.
    function compareHighlights(h1, h2) {
        return h1.characterRange.start - h2.characterRange.start;
    }

    var forEach = [].forEach ?
        function(arr, func) {
            arr.forEach(func);
        } :
        function(arr, func) {
            for (var i = 0, len = arr.length; i < len; ++i) {
                func( arr[i] );
            }
        };

    var nextHighlightId = 1;

    /*----------------------------------------------------------------------------------------------------------------*/

    var highlighterTypes = {};

    function HighlighterType(type, converterCreator) {
        this.type = type;
        this.converterCreator = converterCreator
    }

    HighlighterType.prototype.create = function() {
        var converter = this.converterCreator();
        converter.type = this.type;
        return converter;
    };

    function registerHighlighterType(type, converterCreator) {
        highlighterTypes[type] = new HighlighterType(type, converterCreator);
    }

    function getConverter(type) {
        var highlighterType = highlighterTypes[type];
        if (highlighterType instanceof HighlighterType) {
            return highlighterType.create();
        } else {
            throw new Error("Highlighter type '" + type + "' is not valid")
        }
    }

    api.registerHighlighterType = registerHighlighterType;

    /*----------------------------------------------------------------------------------------------------------------*/

    function CharacterRange(start, end) {
        this.start = start;
        this.end = end;
    }

    CharacterRange.prototype = {
        intersects: function(charRange) {
            return this.start < charRange.end && this.end > charRange.start;
        },

        union: function(charRange) {
            return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
        },

        intersection: function(charRange) {
            return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
        },

        toString: function() {
            return "[CharacterRange(" + this.start + ", " + this.end + ")]";
        }
    };

    CharacterRange.fromCharacterRange = function(charRange) {
        return new CharacterRange(charRange.start, charRange.end);
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    var textContentConverter = {
        rangeToCharacterRange: function(range, containerNode) {
            var bookmark = range.getBookmark(containerNode);
            return new CharacterRange(bookmark.start, bookmark.end);
        },

        characterRangeToRange: function(doc, characterRange, containerNode) {
            var range = api.createRange(doc);
            range.moveToBookmark({
                start: characterRange.start,
                end: characterRange.end,
                containerNode: containerNode
            });

            return range;
        },

        serializeSelection: function(selection, containerNode) {
            var ranges = selection.getAllRanges(), rangeCount = ranges.length;
            var rangeInfos = [];

            var backward = rangeCount == 1 && selection.isBackward();

            for (var i = 0, len = ranges.length; i < len; ++i) {
                rangeInfos[i] = {
                    characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
                    backward: backward
                };
            }

            return rangeInfos;
        },

        restoreSelection: function(selection, savedSelection, containerNode) {
            selection.removeAllRanges();
            var doc = selection.win.document;
            for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
                rangeInfo = savedSelection[i];
                characterRange = rangeInfo.characterRange;
                range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
                selection.addRange(range, rangeInfo.backward);
            }
        }
    };

    registerHighlighterType("textContent", function() {
        return textContentConverter;
    });

    /*----------------------------------------------------------------------------------------------------------------*/

    // Lazily load the TextRange-based converter so that the dependency is only checked when required.
    registerHighlighterType("TextRange", (function() {
        var converter;

        return function() {
            if (!converter) {
                // Test that textRangeModule exists and is supported
                var textRangeModule = api.modules.TextRange;
                if (!textRangeModule) {
                    throw new Error("TextRange module is missing.");
                } else if (!textRangeModule.supported) {
                    throw new Error("TextRange module is present but not supported.");
                }

                converter = {
                    rangeToCharacterRange: function(range, containerNode) {
                        return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
                    },

                    characterRangeToRange: function(doc, characterRange, containerNode) {
                        var range = api.createRange(doc);
                        range.selectCharacters(containerNode, characterRange.start, characterRange.end);
                        return range;
                    },

                    serializeSelection: function(selection, containerNode) {
                        return selection.saveCharacterRanges(containerNode);
                    },

                    restoreSelection: function(selection, savedSelection, containerNode) {
                        selection.restoreCharacterRanges(containerNode, savedSelection);
                    }
                }
            }

            return converter;
        }
    })());

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
        if (id) {
            this.id = id;
            nextHighlightId = Math.max(nextHighlightId, id + 1);
        } else {
            this.id = nextHighlightId++;
        }
        this.characterRange = characterRange;
        this.doc = doc;
        this.classApplier = classApplier;
        this.converter = converter;
        this.containerElementId = containerElementId || null;
        this.applied = false;
    }

    Highlight.prototype = {
        getContainerElement: function() {
            return this.containerElementId ? this.doc.getElementById(this.containerElementId) : getBody(this.doc);
        },

        getRange: function() {
            return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
        },

        fromRange: function(range) {
            this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
        },

        getText: function() {
            return this.getRange().toString();
        },

        containsElement: function(el) {
            return this.getRange().containsNodeContents(el.firstChild);
        },

        unapply: function() {
            this.classApplier.undoToRange(this.getRange());
            this.applied = false;
        },

        apply: function() {
            this.classApplier.applyToRange(this.getRange());
            this.applied = true;
        },

        getHighlightElements: function() {
            return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
        },

        toString: function() {
            return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
                this.characterRange.start + " - " + this.characterRange.end + ")]";
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    function Highlighter(doc, type) {
        type = type || "textContent";
        this.doc = doc || document;
        this.classAppliers = {};
        this.highlights = [];
        this.converter = getConverter(type);
    }

    Highlighter.prototype = {
        addClassApplier: function(classApplier) {
            this.classAppliers[classApplier.cssClass] = classApplier;
        },

        getHighlightForElement: function(el) {
            var highlights = this.highlights;
            for (var i = 0, len = highlights.length; i < len; ++i) {
                if (highlights[i].containsElement(el)) {
                    return highlights[i];
                }
            }
            return null;
        },

        removeHighlights: function(highlights) {
            for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
                highlight = this.highlights[i];
                if (contains(highlights, highlight)) {
                    highlight.unapply();
                    this.highlights.splice(i--, 1);
                }
            }
        },

        removeAllHighlights: function() {
            this.removeHighlights(this.highlights);
        },

        getIntersectingHighlights: function(ranges) {
            // Test each range against each of the highlighted ranges to see whether they overlap
            var intersectingHighlights = [], highlights = this.highlights, converter = this.converter;
            forEach(ranges, function(range) {
                //var selCharRange = converter.rangeToCharacterRange(range);
                forEach(highlights, function(highlight) {
                    if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
                        intersectingHighlights.push(highlight);
                    }
                });
            });

            return intersectingHighlights;
        },

        highlightCharacterRanges: function(className, charRanges, containerElementId) {
            var i, len, j;
            var highlights = this.highlights;
            var converter = this.converter;
            var doc = this.doc;
            var highlightsToRemove = [];
            var classApplier = this.classAppliers[className];
            containerElementId = containerElementId || null;

            var containerElement, containerElementRange, containerElementCharRange;
            if (containerElementId) {
                containerElement = this.doc.getElementById(containerElementId);
                if (containerElement) {
                    containerElementRange = api.createRange(this.doc);
                    containerElementRange.selectNodeContents(containerElement);
                    containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
                    containerElementRange.detach();
                }
            }

            var charRange, highlightCharRange, highlightRange, merged;
            for (i = 0, len = charRanges.length; i < len; ++i) {
                charRange = charRanges[i];
                merged = false;

                // Restrict character range to container element, if it exists
                if (containerElementCharRange) {
                    charRange = charRange.intersection(containerElementCharRange);
                }

                // Check for intersection with existing highlights. For each intersection, create a new highlight
                // which is the union of the highlight range and the selected range
                for (j = 0; j < highlights.length; ++j) {
                    if (containerElementId == highlights[j].containerElementId) {
                        highlightCharRange = highlights[j].characterRange;

                        if (highlightCharRange.intersects(charRange)) {
                            // Replace the existing highlight in the list of current highlights and add it to the list for
                            // removal
                            highlightsToRemove.push(highlights[j]);
                            highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
                        }
                    }
                }

                if (!merged) {
                    highlights.push( new Highlight(doc, charRange, classApplier, converter, null, containerElementId) );
                }
            }

            // Remove the old highlights
            forEach(highlightsToRemove, function(highlightToRemove) {
                highlightToRemove.unapply();
            });

            // Apply new highlights
            var newHighlights = [];
            forEach(highlights, function(highlight) {
                if (!highlight.applied) {
                    highlight.apply();
                    newHighlights.push(highlight);
                }
            });

            return newHighlights;
        },

        highlightRanges: function(className, ranges, containerElement) {
            var selCharRanges = [];
            var converter = this.converter;
            var containerElementId = containerElement ? containerElement.id : null;
            var containerElementRange;
            if (containerElement) {
                containerElementRange = api.createRange(containerElement);
                containerElementRange.selectNodeContents(containerElement);
            }

            forEach(ranges, function(range) {
                var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
                selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
            });

            return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
        },

        highlightSelection: function(className, selection, containerElementId) {
            var converter = this.converter;
            selection = selection || api.getSelection();
            var classApplier = this.classAppliers[className];
            var highlights = this.highlights;
            var doc = selection.win.document;
            var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);

            if (!classApplier) {
                throw new Error("No class applier found for class '" + className + "'");
            }

            // Store the existing selection as character ranges
            var serializedSelection = converter.serializeSelection(selection, containerElement);

            // Create an array of selected character ranges
            var selCharRanges = [];
            forEach(serializedSelection, function(rangeInfo) {
                selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
            });

            var newHighlights = this.highlightCharacterRanges(className, selCharRanges, containerElementId);

            // Restore selection
            converter.restoreSelection(selection, serializedSelection, containerElement);

            return newHighlights;
        },

        unhighlightSelection: function(selection) {
            selection = selection || api.getSelection();
            var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
            this.removeHighlights(intersectingHighlights);
            selection.removeAllRanges();
        },

        selectionOverlapsHighlight: function(selection) {
            selection = selection || api.getSelection();
            return this.getIntersectingHighlights(selection.getAllRanges()).length > 0;
        },

        serialize: function(options) {
            var highlights = this.highlights;
            highlights.sort(compareHighlights);
            var serializedHighlights = ["type:" + this.converter.type];

            forEach(highlights, function(highlight) {
                var characterRange = highlight.characterRange;
                var parts = [
                    characterRange.start,
                    characterRange.end,
                    highlight.id,
                    highlight.classApplier.cssClass,
                    highlight.containerElementId
                ];
                if (options && options.serializeHighlightText) {
                    parts.push(highlight.getText());
                }
                serializedHighlights.push( parts.join("$") );
            });

            return serializedHighlights.join("|");
        },

        deserialize: function(serialized) {
            var serializedHighlights = serialized.split("|");
            var highlights = [];

            var firstHighlight = serializedHighlights[0];
            var regexResult;
            var serializationType, serializationConverter, convertType = false;
            if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
                serializationType = regexResult[1];
                if (serializationType != this.converter.type) {
                    serializationConverter = getConverter(serializationType);
                    convertType = true;
                }
                serializedHighlights.shift();
            } else {
                throw new Error("Serialized highlights are invalid.");
            }

            var classApplier, highlight, characterRange, containerElementId, containerElement;

            for (var i = serializedHighlights.length, parts; i-- > 0; ) {
                parts = serializedHighlights[i].split("$");
                characterRange = new CharacterRange(+parts[0], +parts[1]);
                containerElementId = parts[4] || null;
                containerElement = containerElementId ? this.doc.getElementById(containerElementId) : getBody(this.doc);

                // Convert to the current Highlighter's type, if different from the serialization type
                if (convertType) {
                    characterRange = this.converter.rangeToCharacterRange(
                        serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
                        containerElement
                    );
                }

                classApplier = this.classAppliers[parts[3]];
                highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parts[2], containerElementId);
                highlight.apply();
                highlights.push(highlight);
            }
            this.highlights = highlights;
        }
    };

    api.Highlighter = Highlighter;

    api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
        return new Highlighter(doc, rangeCharacterOffsetConverterType);
    };
});
<|endoftext|>


<commit_before>'use strict';

(function(app) {

  /**
   * Defines the main controller parent of all controllers in the
   * application. All actions not handled in partials are handled
   * by the main controller.
   */
  function MainController($rootScope,
     $scope,
     $location,
     $route,
     authenticationService,
     menuService,
     applicationService,
     userService,
     i18nService,
     alertService,
     entityService,
     $window,
     $q) {

    $scope.displayMainMenu = false;
    $scope.isResponsiveMenuClosed = true;
    $scope.languages = i18nService.getLanguages();
    $scope.language = i18nService.getLanguageName(i18nService.getLanguage());
    $scope.indexOpen = -1;
    $scope.menuDropdownIsOpen = false;
    $scope.newAnimation = '';

    /**
     * Logs out the user.
     * Remove user information, remove all admin informations
     * and broadcast a loggedOut event to children controllers.
     */
    function logout() {
      $scope.closeResponsiveMenu();
      authenticationService.setUserInfo();
      $scope.menu = false;
      $scope.displayMainMenu = false;
      menuService.destroyMenu();
      i18nService.removeDictionary('back-office');
      applicationService.destroy();
      userService.destroy();
      entityService.deleteCache();
      alertService.closeAll();
      $location.path('/login');
    }

    /**
     * Replaces placeholders by corresponding values in the given string.
     * Function copied from AngularJS ngRoute.
     *
     * @param {string} string A string containing placeholders
     * @param {string} params An array of parameters
     * @return {string} interpolation of the redirect path with the parameters
     */
    function interpolate(string, params) {
      var result = [];
      angular.forEach((string || '').split(':'), function(segment, i) {
        if (i === 0) {
          result.push(segment);
        } else {
          var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
          var key = segmentMatch[1];
          result.push(params[key]);
          result.push(segmentMatch[2] || '');
          delete params[key];
        }
      });
      return result.join('');
    }

    /**
     * Replaces placeholders by corresponding values in the given string.
     *
     * @param {string} string A string containing placeholders
     * @param {string} params An array of parameters
     * @return {string} The compiled string
     */
    function resolvePath(string, params) {
      params = angular.copy(params);

      if (params && Object.keys(params).length)
        return interpolate(string, params);
      else
        return string;
    }

    $scope.toggleResponsiveMenu = function() {
      $scope.isResponsiveMenuClosed = !$scope.isResponsiveMenuClosed;
    };

    $scope.closeResponsiveMenu = function() {
      if (!$scope.isResponsiveMenuClosed && $scope.displayMainMenu)
        $scope.isResponsiveMenuClosed = true;
    };

    $scope.openResponsiveMenu = function() {
      if ($scope.isResponsiveMenuClosed && $scope.displayMainMenu)
        $scope.isResponsiveMenuClosed = false;
    };

    $scope.navigate = function(event) {
      if (event) {
        $scope.closeResponsiveMenu();
        $location.path(angular.element(event.currentTarget).attr('href'));
      }
    };

    /**
     * Changes the language to the given one.
     *
     * This will reload the page due to the main menu which can't be
     * reloaded.
     *
     * @param {String} The new language code (e.g. fr_FR)
     */
    $scope.changeLanguage = function(language) {
      i18nService.setLanguage(language);
      $window.location.reload();
    };

    $scope.toggleSidebarSubMenu = function(id) {
      if ($scope.indexOpen == -1)
        $scope.indexOpen = id;
      else if ($scope.indexOpen == id)
        $scope.indexOpen = -1;
      else
        $scope.indexOpen = id;
    };

    /**
     * Logs out the actual user.
     * This will remove user information and redirect to the login
     * page.
     */
    $scope.logout = function() {
      authenticationService.logout().then(logout, logout);
    };

    // Listen to alert add
    $scope.$on('setAlert', function(event, type, msg, timeout) {
      alertService.add(type, msg, timeout);
    });

    // Listen to logout request event to logout the user
    $scope.$on('logout', function() {
      $scope.logout();
    });

    // Listen to forceLogout request event to logout the user
    // without making a call to the server
    $scope.$on('forceLogout', function() {
      logout();
    });

    // Listen to angular router change start event
    // Some promises, like translations and back end menu must be retrieved
    // before routing to an authenticated page
    // Also check if the user is authenticated to grant him access to an authenticated page.
    $scope.$on('$routeChangeStart', function(event, route) {
      var expectedParams = route.params;
      var expectedPath = resolvePath(route.originalPath, expectedParams);

      // No destination route, nothing to do
      if (!expectedPath)
        return false;

      // Retrieve user information and back end translations
      var userInfo = authenticationService.getUserInfo();
      var translations = i18nService.getDictionary('back-office', i18nService.getLanguage());

      if ($location.path() !== '/login' && userInfo && (!menuService.getMenu() || !translations)) {

        // User has access to the back end
        // Some promises are required to access the back end
        // Prevent router from routing to the specified destination and retrieve promises first

        event.preventDefault();

        // List of promises required to access an authenticated page
        var initPromises = {
          i18nCommon: i18nService.addDictionary('common'),
          i18nBE: i18nService.addDictionary('back-office', true),
          menu: menuService.loadMenu()
        };

        $q.all(initPromises).then(
          function(values) {

            // Got enough information to display the back end
            // Just resume the route
            if (expectedPath === resolvePath($location.path(), expectedParams))
              $route.reload();
            else
              $location.path(expectedPath);

          },
          function(error) {

            // User is not authorized to access the back end page
            // It means that its session has expired
            // Cleanly logout the user
            if (error.status === 401) {
              logout();
              $route.reload();
            }

          }
        );

        return false;
      } else if ($location.path() !== '/login' && !userInfo) {

        // User is not authenticated and tries to access the back end
        // Redirect to login page

        event.preventDefault();
        $location.path('/login');
        return false;
      } else if ($location.path() !== '/login' && userInfo) {

        // User is authenticated and tries to access a back end page

        // Check if user has access to the expected page
        $scope.userInfo = userInfo;
        if (route.access && !$scope.checkAccess(route.access)) {
          event.preventDefault();
          $location.path('/');
          return false;
        }

        if (event.targetScope.newAnimation == 'LR')
          event.currentScope.newAnimation = 'RL';
        else if (event.targetScope.newAnimation == 'RL')
          event.currentScope.newAnimation = 'LR';
        else
          event.currentScope.newAnimation = '';
      } else if ($location.path() === '/login' && userInfo) {
        event.preventDefault();
        $location.path('/');
        return false;
      }

    });

    // Listen to route change success event to
    // set new page title and offers access to the menu if
    // user is authenticated
    $scope.$on('$routeChangeSuccess', function(event, route) {
      entityService.deleteCache();
      $scope.userInfo = authenticationService.getUserInfo();
      if ($scope.userInfo) {
        $scope.menu = menuService.getMenu();
        $scope.displayMainMenu = ($scope.menu) ? true : false;
        menuService.setActiveMenuItem();
      } else
        $scope.displayMainMenu = false;

      // Change page title
      $scope.title = $route.current && $route.current.title || '';
      $scope.newAnimation = $rootScope.newAnimation;
    });

    // Listen to the route change error event
    // If user is not authenticated, redirect to the login page
    // otherwise redirect to the home page
    $scope.$on('$routeChangeError', function(event, current, previous, eventObj) {
      if (!$scope.userInfo)
        $location.path('/login');
      else if (eventObj && eventObj.redirect)
        $location.path(eventObj.redirect);
      else $location.path('/');
    });

    $scope.checkAccess = function(perm) {
      if ($scope.userInfo) {

        // Access granted to admin
        if ($scope.userInfo.id == 0) return true;

        // Access granted to user with roles and with the right permissions
        if ($scope.userInfo.roles && $scope.userInfo.roles.length != 0 &&
                $scope.userInfo.permissions && $scope.userInfo.permissions.length != 0)
          return $scope.userInfo.permissions.indexOf(perm) >= 0;

        // Access refused to user with no roles or no permissions
        else return false;
      } else {

        // Access refused if user is not authenticated
        return false;

      }
    };

  }

  app.controller('MainController', MainController);
  MainController.$inject = [
    '$rootScope',
    '$scope',
    '$location',
    '$route',
    'authenticationService',
    'menuService',
    'applicationService',
    'userService',
    'i18nService',
    'alertService',
    'entityService',
    '$window',
    '$q'
  ];

})(angular.module('ov'));
<commit_msg>Correct comment on $scope.changeLanguage method<commit_after>'use strict';

(function(app) {

  /**
   * Defines the main controller parent of all controllers in the
   * application. All actions not handled in partials are handled
   * by the main controller.
   */
  function MainController($rootScope,
     $scope,
     $location,
     $route,
     authenticationService,
     menuService,
     applicationService,
     userService,
     i18nService,
     alertService,
     entityService,
     $window,
     $q) {

    $scope.displayMainMenu = false;
    $scope.isResponsiveMenuClosed = true;
    $scope.languages = i18nService.getLanguages();
    $scope.language = i18nService.getLanguageName(i18nService.getLanguage());
    $scope.indexOpen = -1;
    $scope.menuDropdownIsOpen = false;
    $scope.newAnimation = '';

    /**
     * Logs out the user.
     * Remove user information, remove all admin informations
     * and broadcast a loggedOut event to children controllers.
     */
    function logout() {
      $scope.closeResponsiveMenu();
      authenticationService.setUserInfo();
      $scope.menu = false;
      $scope.displayMainMenu = false;
      menuService.destroyMenu();
      i18nService.removeDictionary('back-office');
      applicationService.destroy();
      userService.destroy();
      entityService.deleteCache();
      alertService.closeAll();
      $location.path('/login');
    }

    /**
     * Replaces placeholders by corresponding values in the given string.
     * Function copied from AngularJS ngRoute.
     *
     * @param {string} string A string containing placeholders
     * @param {string} params An array of parameters
     * @return {string} interpolation of the redirect path with the parameters
     */
    function interpolate(string, params) {
      var result = [];
      angular.forEach((string || '').split(':'), function(segment, i) {
        if (i === 0) {
          result.push(segment);
        } else {
          var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
          var key = segmentMatch[1];
          result.push(params[key]);
          result.push(segmentMatch[2] || '');
          delete params[key];
        }
      });
      return result.join('');
    }

    /**
     * Replaces placeholders by corresponding values in the given string.
     *
     * @param {string} string A string containing placeholders
     * @param {string} params An array of parameters
     * @return {string} The compiled string
     */
    function resolvePath(string, params) {
      params = angular.copy(params);

      if (params && Object.keys(params).length)
        return interpolate(string, params);
      else
        return string;
    }

    $scope.toggleResponsiveMenu = function() {
      $scope.isResponsiveMenuClosed = !$scope.isResponsiveMenuClosed;
    };

    $scope.closeResponsiveMenu = function() {
      if (!$scope.isResponsiveMenuClosed && $scope.displayMainMenu)
        $scope.isResponsiveMenuClosed = true;
    };

    $scope.openResponsiveMenu = function() {
      if ($scope.isResponsiveMenuClosed && $scope.displayMainMenu)
        $scope.isResponsiveMenuClosed = false;
    };

    $scope.navigate = function(event) {
      if (event) {
        $scope.closeResponsiveMenu();
        $location.path(angular.element(event.currentTarget).attr('href'));
      }
    };

    /**
     * Changes the language to the given one.
     *
     * This will reload the page due to the main menu which can't be
     * reloaded.
     *
     * @param {String} language The new language code (e.g. fr_FR)
     */
    $scope.changeLanguage = function(language) {
      i18nService.setLanguage(language);
      $window.location.reload();
    };

    $scope.toggleSidebarSubMenu = function(id) {
      if ($scope.indexOpen == -1)
        $scope.indexOpen = id;
      else if ($scope.indexOpen == id)
        $scope.indexOpen = -1;
      else
        $scope.indexOpen = id;
    };

    /**
     * Logs out the actual user.
     * This will remove user information and redirect to the login
     * page.
     */
    $scope.logout = function() {
      authenticationService.logout().then(logout, logout);
    };

    // Listen to alert add
    $scope.$on('setAlert', function(event, type, msg, timeout) {
      alertService.add(type, msg, timeout);
    });

    // Listen to logout request event to logout the user
    $scope.$on('logout', function() {
      $scope.logout();
    });

    // Listen to forceLogout request event to logout the user
    // without making a call to the server
    $scope.$on('forceLogout', function() {
      logout();
    });

    // Listen to angular router change start event
    // Some promises, like translations and back end menu must be retrieved
    // before routing to an authenticated page
    // Also check if the user is authenticated to grant him access to an authenticated page.
    $scope.$on('$routeChangeStart', function(event, route) {
      var expectedParams = route.params;
      var expectedPath = resolvePath(route.originalPath, expectedParams);

      // No destination route, nothing to do
      if (!expectedPath)
        return false;

      // Retrieve user information and back end translations
      var userInfo = authenticationService.getUserInfo();
      var translations = i18nService.getDictionary('back-office', i18nService.getLanguage());

      if ($location.path() !== '/login' && userInfo && (!menuService.getMenu() || !translations)) {

        // User has access to the back end
        // Some promises are required to access the back end
        // Prevent router from routing to the specified destination and retrieve promises first

        event.preventDefault();

        // List of promises required to access an authenticated page
        var initPromises = {
          i18nCommon: i18nService.addDictionary('common'),
          i18nBE: i18nService.addDictionary('back-office', true),
          menu: menuService.loadMenu()
        };

        $q.all(initPromises).then(
          function(values) {

            // Got enough information to display the back end
            // Just resume the route
            if (expectedPath === resolvePath($location.path(), expectedParams))
              $route.reload();
            else
              $location.path(expectedPath);

          },
          function(error) {

            // User is not authorized to access the back end page
            // It means that its session has expired
            // Cleanly logout the user
            if (error.status === 401) {
              logout();
              $route.reload();
            }

          }
        );

        return false;
      } else if ($location.path() !== '/login' && !userInfo) {

        // User is not authenticated and tries to access the back end
        // Redirect to login page

        event.preventDefault();
        $location.path('/login');
        return false;
      } else if ($location.path() !== '/login' && userInfo) {

        // User is authenticated and tries to access a back end page

        // Check if user has access to the expected page
        $scope.userInfo = userInfo;
        if (route.access && !$scope.checkAccess(route.access)) {
          event.preventDefault();
          $location.path('/');
          return false;
        }

        if (event.targetScope.newAnimation == 'LR')
          event.currentScope.newAnimation = 'RL';
        else if (event.targetScope.newAnimation == 'RL')
          event.currentScope.newAnimation = 'LR';
        else
          event.currentScope.newAnimation = '';
      } else if ($location.path() === '/login' && userInfo) {
        event.preventDefault();
        $location.path('/');
        return false;
      }

    });

    // Listen to route change success event to
    // set new page title and offers access to the menu if
    // user is authenticated
    $scope.$on('$routeChangeSuccess', function(event, route) {
      entityService.deleteCache();
      $scope.userInfo = authenticationService.getUserInfo();
      if ($scope.userInfo) {
        $scope.menu = menuService.getMenu();
        $scope.displayMainMenu = ($scope.menu) ? true : false;
        menuService.setActiveMenuItem();
      } else
        $scope.displayMainMenu = false;

      // Change page title
      $scope.title = $route.current && $route.current.title || '';
      $scope.newAnimation = $rootScope.newAnimation;
    });

    // Listen to the route change error event
    // If user is not authenticated, redirect to the login page
    // otherwise redirect to the home page
    $scope.$on('$routeChangeError', function(event, current, previous, eventObj) {
      if (!$scope.userInfo)
        $location.path('/login');
      else if (eventObj && eventObj.redirect)
        $location.path(eventObj.redirect);
      else $location.path('/');
    });

    $scope.checkAccess = function(perm) {
      if ($scope.userInfo) {

        // Access granted to admin
        if ($scope.userInfo.id == 0) return true;

        // Access granted to user with roles and with the right permissions
        if ($scope.userInfo.roles && $scope.userInfo.roles.length != 0 &&
                $scope.userInfo.permissions && $scope.userInfo.permissions.length != 0)
          return $scope.userInfo.permissions.indexOf(perm) >= 0;

        // Access refused to user with no roles or no permissions
        else return false;
      } else {

        // Access refused if user is not authenticated
        return false;

      }
    };

  }

  app.controller('MainController', MainController);
  MainController.$inject = [
    '$rootScope',
    '$scope',
    '$location',
    '$route',
    'authenticationService',
    'menuService',
    'applicationService',
    'userService',
    'i18nService',
    'alertService',
    'entityService',
    '$window',
    '$q'
  ];

})(angular.module('ov'));
<|endoftext|>