UNPKG

html-truncate-ts

Version:

TypeScript library for truncating HTML strings while preserving HTML tags and structure

162 lines (156 loc) 6.28 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HtmlTruncate = {})); })(this, (function (exports) { 'use strict'; const EMPTY_OBJECT = {}; const EMPTY_STRING = ''; const DEFAULT_TRUNCATE_SYMBOL = '...'; const EXCLUDE_TAGS = ['img', 'br']; const KEY_VALUE_REGEX = '([\\w|-]+\\s*=\\s*"[^"]*"\\s*)*'; const IS_CLOSE_REGEX = '\\s*\\/?\\s*'; const CLOSE_REGEX = '\\s*\\/\\s*'; const SELF_CLOSE_REGEX = new RegExp('<\\/?\\w+\\s*' + KEY_VALUE_REGEX + CLOSE_REGEX + '>'); const HTML_TAG_REGEX = new RegExp('<\\/?\\w+\\s*' + KEY_VALUE_REGEX + IS_CLOSE_REGEX + '>'); const URL_REGEX = /(((ftp|https?):\/\/)[-\w@:%_+.~#?,&//=]+)|((mailto:)?[_.\w-]+@([\w][\w-]+\.)+[a-zA-Z]{2,3})/g; const IMAGE_TAG_REGEX = new RegExp('<img\\s*' + KEY_VALUE_REGEX + IS_CLOSE_REGEX + '>'); const WORD_BREAK_REGEX = new RegExp('\\W+', 'g'); const removeImageTag = (string) => { const match = IMAGE_TAG_REGEX.exec(string); let index, len; if (!match) { return string; } index = match.index; len = match[0].length; return string.substring(0, index) + string.substring(index + len); }; const dumpCloseTag = (tags) => { let html = ''; tags.reverse().forEach((tag) => { if (-1 === EXCLUDE_TAGS.indexOf(tag)) { html += '</' + tag + '>'; } }); return html; }; const getTag = (string) => { let tail = string.indexOf(' '); if (-1 === tail) { tail = string.indexOf('>'); if (-1 === tail) { throw new Error('HTML tag is not well-formed : ' + string); } } return string.substring(1, tail); }; const getEndPosition = (string, maxLength, total, tailPos, options) => { const DEFAULT_SLOP = 10 > maxLength ? maxLength : 10; const { truncateLastWord, slop } = options || {}; const defaultPos = maxLength - total; const isShort = defaultPos < (slop || DEFAULT_SLOP); const slopPos = isShort ? defaultPos : (slop || DEFAULT_SLOP) - 1; const startSlice = isShort ? 0 : defaultPos - (slop || DEFAULT_SLOP); const endSlice = tailPos || defaultPos + (slop || DEFAULT_SLOP); let position = defaultPos, substr, result; if (!truncateLastWord) { substr = string.slice(startSlice, endSlice); if (tailPos && substr.length <= tailPos) { position = substr.length; } else { while ((result = WORD_BREAK_REGEX.exec(substr)) !== null) { if (result.index < slopPos) { position = defaultPos - (slopPos - result.index); if (result.index === 0 && defaultPos <= 1) break; } else if (result.index === slopPos) { position = defaultPos; break; } else { position = defaultPos + (result.index - slopPos); break; } } } if (string.charAt(position - 1).match(/\s$/)) position--; } return position; }; const truncate = (string, maxLength, options) => { const DEFAULT_SLOP = 10 > maxLength ? maxLength : 10; const items = []; let total = 0; let content = EMPTY_STRING; let matches; let result; let index; let tag; let selfClose; options = options || EMPTY_OBJECT; options.ellipsis = undefined !== options.ellipsis ? options.ellipsis : DEFAULT_TRUNCATE_SYMBOL; options.truncateLastWord = undefined !== options.truncateLastWord ? options.truncateLastWord : true; options.slop = undefined !== options.slop ? options.slop : DEFAULT_SLOP; while ((matches = HTML_TAG_REGEX.exec(string))) { result = matches[0]; index = matches.index; if (total + index > maxLength) { content += string.substring(0, getEndPosition(string, maxLength, total, index, options)); break; } else { total += index; content += string.substring(0, index); } if ('/' === result[1]) { items.pop(); selfClose = null; } else { selfClose = SELF_CLOSE_REGEX.exec(result); if (!selfClose) { tag = getTag(result); items.push(tag); } } if (selfClose) { content += selfClose[0]; } else { content += result; } string = string.substring(index + result.length); } if (total >= maxLength) ; else { matches = URL_REGEX.exec(string); if (!matches || matches.index >= maxLength) { content += string.substring(0, getEndPosition(string, maxLength, total, undefined, options)); } else { while (matches) { result = matches[0]; index = matches.index; content += string.substring(0, index + result.length - total); string = string.substring(index + result.length); matches = URL_REGEX.exec(string); } } } if (string.length > maxLength - total && options.ellipsis) { content += options.ellipsis; } content += dumpCloseTag(items); if (!options.keepImageTag) { content = removeImageTag(content); } return content; }; exports.truncate = truncate; })); //# sourceMappingURL=html-truncate.js.map