1/*! 2 * Clamp.js 0.11.5 3 * 4 * Now Maintained by Josh English http://joshengish.com 5 * Joseph Schmitt http://joe.sh 6 * Released under the WTFPL license 7 * http://sam.zoy.org/wtfpl/ 8 */ 9 10(function(root, factory) { 11 if (typeof define === 'function' && define.amd) { 12 // AMD 13 define([], factory); 14 } else if (typeof exports === 'object') { 15 // Node, CommonJS-like 16 module.exports = {clamp: factory()}; 17 } else { 18 // Browser globals 19 root.$clamp = factory(); 20 } 21}(this, function() { 22 /** 23 * Clamps a text node. 24 * @param {HTMLElement} element. Element containing the text node to clamp. 25 * @param {Object} options. Options to pass to the clamper. 26 */ 27 function clamp(element, options) { 28 options = options || {}; 29 30 var self = this, 31 win = window, 32 opt = { 33 clamp: options.clamp || 2, 34 useNativeClamp: typeof(options.useNativeClamp) != 'undefined' ? options.useNativeClamp : true, 35 splitOnChars: options.splitOnChars || ['.', '-', 'â', 'â', ' '], //Split on sentences (periods), hypens, en-dashes, em-dashes, and words (spaces). 36 animate: options.animate || false, 37 truncationChar: options.truncationChar || 'â¦', 38 truncationHTML: options.truncationHTML 39 }, 40 41 sty = element.style, 42 originalText = element.innerHTML, 43 44 supportsNativeClamp = typeof(element.style.webkitLineClamp) != 'undefined', 45 clampValue = opt.clamp, 46 isCSSValue = clampValue.indexOf && (clampValue.indexOf('px') > -1 || clampValue.indexOf('em') > -1), 47 truncationHTMLContainer; 48 49 if (opt.truncationHTML) { 50 truncationHTMLContainer = document.createElement('span'); 51 truncationHTMLContainer.innerHTML = opt.truncationHTML; 52 } 53 54 55 // UTILITY FUNCTIONS __________________________________________________________ 56 57 /** 58 * Return the current style for an element. 59 * @param {HTMLElement} elem The element to compute. 60 * @param {string} prop The style property. 61 * @returns {number} 62 */ 63 function computeStyle(elem, prop) { 64 if (!win.getComputedStyle) { 65 win.getComputedStyle = function(el, pseudo) { 66 this.el = el; 67 this.getPropertyValue = function(prop) { 68 var re = /(\-([a-z]){1})/g; 69 if (prop == 'float') prop = 'styleFloat'; 70 if (re.test(prop)) { 71 prop = prop.replace(re, function() { 72 return arguments[2].toUpperCase(); 73 }); 74 } 75 return el.currentStyle && el.currentStyle[prop] ? el.currentStyle[prop] : null; 76 }; 77 return this; 78 }; 79 } 80 81 return win.getComputedStyle(elem, null).getPropertyValue(prop); 82 } 83 84 /** 85 * Returns the maximum number of lines of text that should be rendered based 86 * on the current height of the element and the line-height of the text. 87 */ 88 function getMaxLines(height) { 89 var availHeight = height || element.clientHeight, 90 lineHeight = getLineHeight(element); 91 92 return Math.max(Math.floor(availHeight / lineHeight), 0); 93 } 94 95 /** 96 * Returns the maximum height a given element should have based on the line- 97 * height of the text and the given clamp value. 98 */ 99 function getMaxHeight(clmp) { 100 var lineHeight = getLineHeight(element); 101 // console.log(lineHeight * clmp); 102 return lineHeight * clmp; 103 } 104 105 /** 106 * Returns the line-height of an element as an integer. 107 */ 108 function getLineHeight(elem) { 109 var lh = computeStyle(elem, 'line-height'); 110 if (lh == 'normal') { 111 // Normal line heights vary from browser to browser. The spec recommends 112 // a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff. 113 lh = parseInt(computeStyle(elem, 'font-size')) * 1.187; 114 } 115 return Math.ceil(parseFloat(lh)); 116 } 117 118 119 // MEAT AND POTATOES (MMMM, POTATOES...) ______________________________________ 120 var splitOnChars = opt.splitOnChars.slice(0), 121 splitChar = splitOnChars[0], 122 chunks, 123 lastChunk; 124 125 /** 126 * Gets an element's last child. That may be another node or a node's contents. 127 */ 128 function getLastChild(elem) { 129 if (!elem.lastChild) { 130 return; 131 } 132 //Current element has children, need to go deeper and get last child as a text node 133 if (elem.lastChild.childNodes && elem.lastChild.childNodes.length > 0) { 134 return getLastChild(Array.prototype.slice.call(elem.childNodes).pop()); 135 } 136 //This is the absolute last child, a text node, but something's wrong with it. Remove it and keep trying 137 else if (!elem.lastChild || !elem.lastChild.nodeValue || elem.lastChild.nodeValue === '' || elem.lastChild.nodeValue == opt.truncationChar) { 138 elem.lastChild.parentNode.removeChild(elem.lastChild); 139 return getLastChild(element); 140 } 141 //This is the last child we want, return it 142 else { 143 return elem.lastChild; 144 } 145 } 146 147 /** 148 * Removes one character at a time from the text until its width or 149 * height is beneath the passed-in max param. 150 */ 151 function truncate(target, maxHeight) { 152 if (!target || !maxHeight) { 153 return; 154 } 155 156 /** 157 * Resets global variables. 158 */ 159 function reset() { 160 splitOnChars = opt.splitOnChars.slice(0); 161 splitChar = splitOnChars[0]; 162 chunks = null; 163 lastChunk = null; 164 } 165 // console.log(splitOnChars); 166 167 var nodeValue = target.nodeValue.replace(opt.truncationChar, ''); 168 169 //Grab the next chunks 170 if (!chunks) { 171 //If there are more characters to try, grab the next one 172 if (splitOnChars.length > 0) { 173 splitChar = splitOnChars.shift(); 174 } 175 //No characters to chunk by. Go character-by-character 176 else { 177 splitChar = ''; 178 } 179 180 chunks = nodeValue.split(splitChar); 181 } 182 183 //If there are chunks left to remove, remove the last one and see if 184 // the nodeValue fits. 185 if (chunks.length > 1) { 186 // console.log('chunks', chunks); 187 lastChunk = chunks.pop(); 188 // console.log('lastChunk', lastChunk); 189 applyEllipsis(target, chunks.join(splitChar)); 190 } 191 //No more chunks can be removed using this character 192 else { 193 chunks = null; 194 } 195 196 //Insert the custom HTML before the truncation character 197 if (truncationHTMLContainer) { 198 target.nodeValue = target.nodeValue.replace(opt.truncationChar, ''); 199 element.innerHTML = target.nodeValue + ' ' + truncationHTMLContainer.innerHTML + opt.truncationChar; 200 } 201 202 //Search produced valid chunks 203 if (chunks) { 204 //It fits 205 if (element.clientHeight <= maxHeight) { 206 // console.log(element.clientHeight, maxHeight); 207 //There's still more characters to try splitting on, not quite done yet 208 if (splitOnChars.length >= 0 && splitChar !== '') { 209 applyEllipsis(target, chunks.join(splitChar) + splitChar + lastChunk); 210 chunks = null; 211 } 212 //Finished! 213 else { 214 return element.innerHTML; 215 } 216 } 217 } 218 //No valid chunks produced 219 else { 220 //No valid chunks even when splitting by letter, time to move 221 //on to the next node 222 if (splitChar === '') { 223 applyEllipsis(target, ''); 224 target = getLastChild(element); 225 226 reset(); 227 } 228 } 229
230 //If you get here it means still too big, let's keep truncating 231 if (opt.animate) { 232 setTimeout(function() { 233 truncate(target, maxHeight); 234 }, opt.animate === true ? 10 : opt.animate); 235 } else { 236 return truncate(target, maxHeight); 237 } 238 } 239 240 function applyEllipsis(elem, str) { 241 elem.nodeValue = str + opt.truncationChar; 242 } 243 244 245 // CONSTRUCTOR ________________________________________________________________ 246 247 if (clampValue == 'auto') { 248 clampValue = getMaxLines(); 249 } else if (isCSSValue) { 250 clampValue = getMaxLines(parseInt(clampValue)); 251 } 252 253 var clampedText; 254 if (supportsNativeClamp && opt.useNativeClamp) { 255 sty.overflow = 'hidden'; 256 sty.textOverflow = 'ellipsis'; 257 sty.webkitBoxOrient = 'vertical'; 258 sty.display = '-webkit-box'; 259 sty.webkitLineClamp = clampValue; 260 261 if (isCSSValue) { 262 sty.height = opt.clamp + 'px'; 263 } 264 } else { 265 var height = getMaxHeight(clampValue); 266 if (height < element.clientHeight) { 267 clampedText = truncate(getLastChild(element), height); 268 } 269 } 270 271 return { 272 'original': originalText, 273 'clamped': clampedText 274 }; 275 } 276 277 return clamp; 278}));
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.