blob: 09bbc47fa1d1ce06e77f5573e02563e073475c2f [file] [log] [blame]
/*!
* 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;