1/* global jQuery */ 2 3/* 4 * Highlighter.js 1.0 5 * 6 * Author: Matthew Conlen <[email protected]> 7 * Huffington Post Labs 8 * 9 * Copyright 2012: Huffington Post Labs 10 * 11 * This program is free software. It comes without any warranty, to 12 * the extent permitted by applicable law. You can redistribute it 13 * and/or modify it under the terms of the WTFPL, Version 2, as 14 * published by Sam Hocevar. See http://sam.zoy.org/wtfpl/ 15 * for more details. 16 */ 17 18 (function ($) { 19 /* 20 * Code for triple click from 21 * http://css-tricks.com/snippets/jquery/triple-click-event/ 22 */ 23 $.event.special.tripleclick = { 24 25 setup: function (data, namespaces) { 26 var elem = this, 27 $elem = jQuery(elem); 28 $elem.bind('click', jQuery.event.special.tripleclick.handler); 29 }, 30 31 teardown: function (namespaces) { 32 var elem = this, 33 $elem = jQuery(elem); 34 $elem.unbind('click', jQuery.event.special.tripleclick.handler); 35 }, 36 37 handler: function (event) { 38 var elem = this, 39 $elem = jQuery(elem), 40 clicks = $elem.data('clicks') || 0; 41 clicks += 1; 42 if (clicks === 3) { 43 clicks = 0; 44 45 // set event type to "tripleclick" 46 event.type = "tripleclick"; 47 48 // let jQuery handle the triggering of "tripleclick" event handlers 49 jQuery.event.dispatch.apply(this, arguments); 50 } 51 $elem.data('clicks', clicks); 52 } 53 }; 54 55 /* 56 * Attempt to get the previous sibling 57 * of a container in the event of a triple 58 * click. 59 * 60 * Adapted from http://stackoverflow.com/a/574922 61 */ 62 function get_previoussibling(n) { 63 var y = n, x; 64 try { 65 x = n.previousSibling; 66 while (x && x.nodeType != 1) { 67 y = x; 68 x = x.previousSibling; 69 } 70 } catch (err) { 71 console.log(err); 72 topOffset = -15; 73 return y; 74 } 75 return x ? x : y; 76 } 77 78 var methods = { 79 init: function (options) { 80 81 var settings = $.extend({ 82 'selector': '.highlighter-container', 83 'minWords': 0, 84 'complete': function() {} 85 }, options); 86 var numClicks = 0; 87 var topOffset = 0; 88 var leftOffset = 0; 89 var isDown = false; 90 91 var selText; 92 93 return this.each(function () { 94 /* 95 * Insert an html <span> after a user selects text. 96 * We then use the X-Y coordinates of that span 97 * to place our tooltip. 98 * Thanks to http://stackoverflow.com/a/3599599 for 99 * some inspiration. 100 */ 101 function insertSpanAfterSelection(clicks) { 102 var html = "<span class='dummy'><span>"; 103 topOffset = 0; 104 leftOffset = 0; 105 if (numClicks !== clicks) return; 106 var isIE = (navigator.appName === "Microsoft Internet Explorer"); 107 var sel, range, expandedSelRange, node; 108 var position; 109 if (window.getSelection) { 110 sel = window.getSelection(); 111 selText = sel.toString(); 112 113 if ($.trim(selText) === '' || selText.split(' ').length < settings.minWords) return; 114 115 if (sel.getRangeAt && sel.rangeCount) { 116 range = window.getSelection().getRangeAt(0); 117 118 expandedSelRange = range.cloneRange(); 119 expandedSelRange.collapse(false); 120 121 // Range.createContextualFragment() would be useful here but is 122 // non-standard and not supported in all browsers (IE9, for one) 123 var el = document.createElement("div"); 124 el.innerHTML = html; 125 var dummy = document.createElement("span"); 126 127 if (range.startOffset === 0 && range.endOffset === 0) { 128 129 var cont = expandedSelRange.startContainer; 130 var prev = get_previoussibling(cont); 131 try { 132 expandedSelRange.selectNode(prev.lastChild); 133 } catch (err) { 134 leftOffset = 40; 135 topOffset = -15; 136 expandedSelRange.selectNode(prev); 137 } 138 // console.log(expandedSelRange); 139 expandedSelRange.collapse(false); 140 } else if(range.endOffset === 0 ) { 141 topOffset = -25; 142 leftOffset = 40; 143 } 144 145 146 if (numClicks !== clicks) return; 147 $(settings.selector).hide(); 148 if (!isIE && $.trim(selText) === $.trim(expandedSelRange.startContainer.innerText)) { 149 expandedSelRange.startContainer.innerHTML += "<span class='dummy'> </span>"; 150 position = $(".dummy").offset(); 151 $(".dummy").remove(); 152 } else if (!isIE && $.trim(selText) === $.trim(expandedSelRange.endContainer.innerText)) { 153 expandedSelRange.endContainer.innerHTML += "<span class='dummy'> </span>"; 154 position = $(".dummy").offset(); 155 $(".dummy").remove(); 156 } else {
157 expandedSelRange.insertNode(dummy); 158 position = $(dummy).offset(); 159 dummy.parentNode.removeChild(dummy); 160 } 161 } 162 } else if (document.selection && document.selection.createRange) { 163 range = document.selection.createRange(); 164 expandedSelRange = range.duplicate(); 165 166 selText = expandedSelRange.text; 167 if ($.trim(selText) === '' || selText.split(' ').length < settings.minWords) return; 168 169 range.collapse(false); 170 range.pasteHTML(html); 171 172 expandedSelRange.setEndPoint("EndToEnd", range); 173 expandedSelRange.select(); 174 position = $(".dummy").offset(); 175 $(".dummy").remove(); 176 } 177 178 $(settings.selector).css("top", position.top + topOffset + "px"); 179 $(settings.selector).css("left", position.left + leftOffset + "px"); 180 $(settings.selector).show(300, function() { 181 settings.complete(selText); 182 }); 183 } 184 $(settings.selector).hide(); 185 $(settings.selector).css("position", "absolute"); 186 $(document).bind('mouseup.highlighter', function (e) { 187 if (isDown) { 188 numClicks = 1; 189 clicks = 0; 190 setTimeout(function () { 191 insertSpanAfterSelection(1); 192 }, 300); 193 isDown = false; 194 } 195 }); 196 $(this).bind('mouseup.highlighter', function (e) { 197 numClicks = 1; 198 clicks = 0; 199 setTimeout(function () { 200 insertSpanAfterSelection(1); 201 }, 300); 202 }); 203 $(this).bind('tripleclick.highlighter', function (e) { 204 numClicks = 3; 205 setTimeout(function () { 206 insertSpanAfterSelection(3); 207 }, 200); 208 }); 209 210 $(this).bind('dblclick.highlighter', function (e) { 211 numClicks = 2; 212 setTimeout(function () { 213 insertSpanAfterSelection(2); 214 }, 300); 215 }); 216 $(this).bind('mousedown.highlighter', function (e) { 217 $(settings.selector).hide(); 218 isDown = true; 219 }); 220 221 }); 222 }, 223 destroy: function (content) { 224 return this.each(function () { 225 $(document).unbind('mouseup.highlighter'); 226 $(this).unbind('mouseup.highlighter'); 227 $(this).unbind('tripleclick.highlighter'); 228 $(this).unbind('dblclick.highlighter'); 229 $(this).unbind('mousedown.highlighter'); 230 }); 231 } 232 }; 233 234 /* 235 * Method calling logic taken from the 236 * jQuery article on best practices for 237 * plugins. 238 * 239 * http://docs.jquery.com/Plugins/Authoring 240 */ 241 $.fn.highlighter = function (method) { 242 if (methods[method]) { 243 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 244 } else if (typeof method === 'object' || !method) { 245 return methods.init.apply(this, arguments); 246 } else { 247 $.error('Method ' + method + ' does not exist on jQuery.highlighter'); 248 } 249 250 }; 251 252})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.