/*global cull, dome*/ this.gts = this.gts || {}; /** * Utilities for working with syntax highligted blobs. Assumes the markup for * blobs generated by Dolt. Exports three functions: * * * trackFocus * Sets up events to highlight lines on mouseover, and allows the user to * highlight regions * * regionFromUrl * Parses the URL and finds line number or region to highlight * * highlightRegion * Highlights a region * * The following functions all assume that a blob is contained in some root * element that represents each line in the file as an li element. * * A "region" is an array of two numbers, denoting the first and last line of * a consecutive region. Both boundaries are inclusive. Regions start at 1 (not * 0). * [1, 2] - The region consisting of the first and second line, inclusive * [1, 1] - The first line * [10, 14] - The region of lines 10, 11, 12, 13 and 14 */ this.gts.blob = (function () { /** * Accepts the root element of a blob and a region, and adds the "region" * class name to all li elements in the region. */ function highlightRegion(element, region) { // Line numbers are 1-based, but ElementLists are 0-indexed, // offset region accordingly var start = region[0] - 1; var end = region[1] - 1; var lines = element.getElementsByTagName("li"); cull.doall(function (li, num) { var inRegion = start <= num && num <= end; dome.cn[inRegion ? "add" : "rm"]("region", li); }, element.getElementsByTagName("li")); } /** * Given an li element representing a line, return which line number it * represents. */ function getLineNum(element) { while (element && element.tagName !== "LI") { element = element.parentNode; } var lineNum = element && element.className.match(/L(\d+)/)[1]; if (!lineNum) { return; } return parseInt(lineNum, 10); } /** * Set the active region. Marks the region and updates the URL with the * new region as an anchor ("#L-"). */ function setRegion(element, region, redirect) { var regionStr = region[0]; regionStr += region[1] !== region[0] ? "-" + region[1] : ""; redirect("#L" + regionStr); highlightRegion(element, region); } /** * Given two numbers, return a region where the lowest number is the start * and the highest number is the end. The end is optional, in which case * the [start, start] region is returned. */ function orderedRegion(start, end) { end = typeof end === "number" ? end : start; return [Math.min(start, end), Math.max(start, end)]; } /** * Track selected region through clicks on lines. * * Clicking a line highlights it by setting the region to that line * (e.g [n, n]). * * Holding shift while clicking a line makes a region starting at the * previously clicked line and ending at the line being clicked. Example: * * Click on line 4. Press shift, click line 9. Lines 4, 5, 6, 7, 8 and 9 * will be highlighted. While holding shift, click line 1. Lines 1, 2, 3 and * 4 will be highlighted. Release shift, click line 5, line 5 (and no * others) will be highlighted. */ function trackSelectedRegion(element, redirect) { var shift = false, anchor; var events = []; dome.on(document.body, "keydown", function (e) { shift = e.which === 16; }); dome.on(document.body, "keyup", function (e) { shift = e.which !== 16; }); dome.on(element, "click", function (e) { var region, currLine = getLineNum(e.target); if (!shift || !anchor) { anchor = e.target; } if (anchor && shift) { region = orderedRegion(getLineNum(anchor), currLine); e.preventDefault(); } else { region = [currLine, currLine]; } setRegion(element, region, redirect); }); } /** * Track focus on lines by highlighting lines as the cursor hovers them, and * by highlighting (and selecting) regions as lines are clicked (see the * above function) */ function trackFocus(element, redirect) { var highlight = function () { dome.cn.add("focus", this); }; var dim = function () { dome.cn.rm("focus", this); }; cull.doall(function (li) { dome.on(li, "mouseenter", highlight); dome.on(li, "mouseleave", dim); }, element.getElementsByTagName("li")); trackSelectedRegion(element, redirect); } /** * Regions can be specified over the URL by using the #L[-] * syntax. This function extracts it. Examples: * * /some/url#L1 - [1, 1] * /some/url#L1-2 - [1, 2] * /some/url#l12-2 - [2, 12] (ordered) */ function regionFromUrl(url) { var matches = url.match(/\#l(\d+)(?:-(\d+))?/i); if (!matches) { return; } return orderedRegion(matches[1], matches[2] && Number(matches[2])); } return { trackFocus: trackFocus, regionFromUrl: regionFromUrl, highlightRegion: highlightRegion }; }());