| /*! |
| * trancate-html v1.0.3 |
| * Copyright© 2019 Saiya https://github.com/evecalm/truncate-html#readme |
| */ |
| // Modified from https://github.com/oe/truncate-html/blob/master/dist/truncate.cjs.js |
| 'use strict'; |
| |
| // default options |
| var defaultOptions = { |
| // remove all tags |
| stripTags: false, |
| // postfix of the string |
| ellipsis: '...', |
| // decode html entities |
| decodeEntities: false, |
| // whether truncate by words |
| byWords: false, |
| // // truncate by words, set to true keep words |
| // // set to number then truncate by word count |
| // length: 0 |
| excludes: '', |
| reserveLastWord: false, |
| trimTheOnlyWord: false, |
| keepWhitespaces: false // even if set true, continuous whitespace will count as one |
| }; |
| var astralRange = /\ud83c[\udffb-\udfff](?=\ud83c[\udffb-\udfff])|(?:[^\ud800-\udfff][\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]?|[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/g; |
| // helper method |
| var helper = { |
| setup: function setup(length, options) { |
| switch (typeof length) { |
| case 'object': |
| options = length; |
| break; |
| case 'number': |
| if (typeof options === 'object') { |
| options.length = length; |
| } |
| else { |
| options = { |
| length: length |
| }; |
| } |
| } |
| var fullOptions = this.extend(options, defaultOptions); |
| // if (typeof fullOptions.length !== 'number') throw new TypeError('truncate-html: options.length should be a number') |
| if (fullOptions.excludes) { |
| if (!Array.isArray(fullOptions.excludes)) { |
| fullOptions.excludes = [fullOptions.excludes]; |
| } |
| fullOptions.excludes = fullOptions.excludes.join(','); |
| } |
| this.options = fullOptions; |
| this.limit = fullOptions.length; |
| this.ellipsis = fullOptions.ellipsis; |
| this.keepWhitespaces = fullOptions.keepWhitespaces; |
| this.reserveLastWord = fullOptions.reserveLastWord; |
| this.trimTheOnlyWord = fullOptions.trimTheOnlyWord; |
| }, |
| // extend obj with dft |
| extend: function extend(obj, dft) { |
| if (obj == null) { |
| obj = {}; |
| } |
| for (var k in dft) { |
| var v = dft[k]; |
| if (obj[k] != null) { |
| continue; |
| } |
| obj[k] = v; |
| } |
| return obj; |
| }, |
| // test a char whether a whitespace char |
| isBlank: function isBlank(char) { |
| return (char === ' ' || |
| char === '\f' || |
| char === '\n' || |
| char === '\r' || |
| char === '\t' || |
| char === '\v' || |
| char === '\u00A0' || |
| char === '\u2028' || |
| char === '\u2029'); |
| }, |
| /** |
| * truncate text |
| * @param {String} text text to truncate |
| * @param {Boolean} isLastNode is last dom node, help to decide whether add ellipsis |
| * @return {String} |
| */ |
| truncate: function truncate(text, isLastNode) { |
| if (!this.keepWhitespaces) { |
| text = text.replace(/\s+/g, ' '); |
| } |
| var byWords = this.options.byWords; |
| var match = text.match(astralRange); |
| var astralSafeCharacterArray = match === null ? [] : match; |
| var strLen = match === null ? 0 : astralSafeCharacterArray.length; |
| var idx = 0; |
| var count = 0; |
| var prevIsBlank = byWords; |
| var curIsBlank = false; |
| while (idx < strLen) { |
| curIsBlank = this.isBlank(astralSafeCharacterArray[idx++]); |
| // keep same then continue |
| if (byWords && prevIsBlank === curIsBlank) |
| { continue; } |
| if (count === this.limit) { |
| // reserve trailing whitespace, only when prev is blank too |
| if (prevIsBlank && curIsBlank) { |
| prevIsBlank = curIsBlank; |
| continue; |
| } |
| // fix idx because current char belong to next words which exceed the limit |
| --idx; |
| break; |
| } |
| if (byWords) { |
| curIsBlank || ++count; |
| } |
| else { |
| (curIsBlank && prevIsBlank) || ++count; |
| } |
| prevIsBlank = curIsBlank; |
| } |
| this.limit -= count; |
| if (this.limit) { |
| return text; |
| } |
| else { |
| var str; |
| if (byWords) { |
| str = text.substr(0, idx); |
| } |
| else { |
| str = this.substr(astralSafeCharacterArray, idx); |
| } |
| if (str === text) { |
| // if is lat node, no need of ellipsis, or add it |
| return isLastNode ? text : text + this.ellipsis; |
| } |
| else { |
| return str + this.ellipsis; |
| } |
| } |
| }, |
| // deal with cut string in the middle of a word |
| substr: function substr(astralSafeCharacterArray, len) { |
| // var boundary, cutted, result |
| var cutted = astralSafeCharacterArray.slice(0, len).join(''); |
| if (!this.reserveLastWord || astralSafeCharacterArray.length === len) { |
| return cutted; |
| } |
| var boundary = astralSafeCharacterArray.slice(len - 1, len + 1).join(''); |
| // if truncate at word boundary, just return |
| if (/\W/.test(boundary)) { |
| return cutted; |
| } |
| if (this.reserveLastWord < 0) { |
| var result = cutted.replace(/\w+$/, ''); |
| // if the cutted is not the first and the only word |
| // then return result, or return the whole word |
| if (!(result.length === 0 && cutted.length === this.options.length)) { |
| return result; |
| } |
| if (this.trimTheOnlyWord) |
| { return cutted; } |
| } |
| // set max exceeded to 10 if this.reserveLastWord is true or < 0 |
| var maxExceeded = this.reserveLastWord !== true && this.reserveLastWord > 0 |
| ? this.reserveLastWord |
| : 10; |
| var mtc = astralSafeCharacterArray.slice(len).join('').match(/(\w+)/); |
| var exceeded = mtc ? mtc[1] : ''; |
| return cutted + exceeded.substr(0, maxExceeded); |
| } |
| }; |
| |
| /** |
| * truncate html |
| * @method truncate(html, [length], [options]) |
| * @param {String} html html string to truncate |
| * @param {Object|number} length how many letters(words if `byWords` is true) you want reserve |
| * @param {Object|null} options |
| * @return {String} |
| */ |
| var truncate = function (el, length, options) { |
| helper.setup(length, options); |
| if (!el || |
| isNaN(helper.limit) || |
| helper.limit <= 0 || |
| helper.limit === Infinity) { |
| return el; |
| } |
| // if (helper.limit) |
| // remove excludes elements |
| // helper.options.excludes && $html.find(helper.options.excludes).remove(); |
| |
| // strip tags and get pure text |
| if (helper.options.stripTags) { |
| return helper.truncate(el.innerText || el.textContent); |
| } |
| var travelChildren = function ($ele, isParentLastNode) { |
| if ( isParentLastNode === void 0 ) isParentLastNode = true; |
| |
| var contents = $ele.contents(); |
| var lastIdx = contents.length - 1; |
| return contents.each(function (idx) { |
| switch (this.type) { |
| case 'text': |
| if (!helper.limit) { |
| $(this).remove(); |
| return; |
| } |
| this.data = helper.truncate($(this).text(), isParentLastNode && idx === lastIdx); |
| break; |
| case 'tag': |
| if (!helper.limit) { |
| $(this).remove(); |
| } |
| else { |
| return travelChildren($(this), isParentLastNode && idx === lastIdx); |
| } |
| break; |
| default: |
| // for comments |
| return $(this).remove(); |
| } |
| }); |
| }; |
| travelChildren(el); |
| return el.innerHTML; |
| }; |
| truncate.setup = function (options) { |
| if ( options === void 0 ) options = {}; |
| |
| return Object.assign(defaultOptions, options); |
| }; |
| |
| module.exports = truncate; |