UNPKG

vue-truncate-collapsed

Version:

A simple component that truncates your text and adds a 'Read More/Show Less' clickable.

766 lines (701 loc) 22.5 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else { var a = factory(); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } })(typeof self !== 'undefined' ? self : this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // var _h2p = __webpack_require__(4); /* harmony default export */ __webpack_exports__["a"] = ({ name: 'Truncate', props: { truncated: { type: Boolean, "default": true }, collapsedTextClass: { type: String, "default": '' }, text: { type: String, required: true }, clamp: { type: String, "default": 'Read More' }, length: { type: Number, "default": 100 }, less: { type: String, "default": 'Show Less' }, type: { type: String, "default": 'text' }, actionClass: { type: String, "default": '' } }, data: function data() { return { show: false }; }, computed: { isHTML: function isHTML() { return this.type === 'html'; }, textClass: function textClass() { return this.textLength > this.length && this.collapsedTextClass ? this.collapsedTextClass : ''; }, textLength: function textLength() { if (this.isHTML) { // We need the length of the text without the html being considered // This ensures we provide the right calculation for when to show/hide the more link var text = this.text.replace(/<[^>]*>/g, ''); return text.length; } return this.text.length; }, showToggle: function showToggle() { return this.textLength > this.length; } }, watch: { truncated: function truncated(value) { this.toggle(value); } }, created: function created() { this.show = !this.truncated; this.toggle(!this.truncated); }, methods: { truncate: function truncate(string) { if (string) { if (this.type === 'html') return _h2p(string, this.length); return string.toString().substring(0, this.length); } return ''; }, toggle: function toggle(override) { // use the override value if it is set as a boolean var toggled = typeof override === 'boolean' ? override : !this.show; this.show = toggled; this.$emit('toggle', toggled); }, h2p: function h2p(text) { return _h2p(text); } } }); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__truncate_vue__ = __webpack_require__(2); var VueTruncate = { install: function install(Vue) { Vue.component('truncate', __WEBPACK_IMPORTED_MODULE_0__truncate_vue__["a" /* default */]); } }; if (typeof window !== 'undefined' && window.Vue) { window.Vue.use(VueTruncate); } /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_truncate_vue__ = __webpack_require__(0); /* unused harmony namespace reexport */ /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_11b2e33f_hasScoped_false_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_truncate_vue__ = __webpack_require__(5); var disposed = false var normalizeComponent = __webpack_require__(3) /* script */ /* template */ /* template functional */ var __vue_template_functional__ = false /* styles */ var __vue_styles__ = null /* scopeId */ var __vue_scopeId__ = null /* moduleIdentifier (server only) */ var __vue_module_identifier__ = null var Component = normalizeComponent( __WEBPACK_IMPORTED_MODULE_0__babel_loader_node_modules_vue_loader_lib_selector_type_script_index_0_truncate_vue__["a" /* default */], __WEBPACK_IMPORTED_MODULE_1__node_modules_vue_loader_lib_template_compiler_index_id_data_v_11b2e33f_hasScoped_false_buble_transforms_node_modules_vue_loader_lib_selector_type_template_index_0_truncate_vue__["a" /* default */], __vue_template_functional__, __vue_styles__, __vue_scopeId__, __vue_module_identifier__ ) Component.options.__file = "src/truncate.vue" /* hot reload */ if (false) {(function () { var hotAPI = require("vue-hot-reload-api") hotAPI.install(require("vue"), false) if (!hotAPI.compatible) return module.hot.accept() if (!module.hot.data) { hotAPI.createRecord("data-v-11b2e33f", Component.options) } else { hotAPI.reload("data-v-11b2e33f", Component.options) } module.hot.dispose(function (data) { disposed = true }) })()} /* harmony default export */ __webpack_exports__["a"] = (Component.exports); /***/ }), /* 3 */ /***/ (function(module, exports) { /* globals __VUE_SSR_CONTEXT__ */ // IMPORTANT: Do NOT use ES2015 features in this file. // This module is a runtime utility for cleaner component module output and will // be included in the final webpack user bundle. module.exports = function normalizeComponent ( rawScriptExports, compiledTemplate, functionalTemplate, injectStyles, scopeId, moduleIdentifier /* server only */ ) { var esModule var scriptExports = rawScriptExports = rawScriptExports || {} // ES6 modules interop var type = typeof rawScriptExports.default if (type === 'object' || type === 'function') { esModule = rawScriptExports scriptExports = rawScriptExports.default } // Vue.extend constructor export interop var options = typeof scriptExports === 'function' ? scriptExports.options : scriptExports // render functions if (compiledTemplate) { options.render = compiledTemplate.render options.staticRenderFns = compiledTemplate.staticRenderFns options._compiled = true } // functional template if (functionalTemplate) { options.functional = true } // scopedId if (scopeId) { options._scopeId = scopeId } var hook if (moduleIdentifier) { // server build hook = function (context) { // 2.3 injection context = context || // cached call (this.$vnode && this.$vnode.ssrContext) || // stateful (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional // 2.2 with runInNewContext: true if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') { context = __VUE_SSR_CONTEXT__ } // inject component styles if (injectStyles) { injectStyles.call(this, context) } // register component module identifier for async chunk inferrence if (context && context._registeredComponents) { context._registeredComponents.add(moduleIdentifier) } } // used by ssr in case component is cached and beforeCreate // never gets called options._ssrRegister = hook } else if (injectStyles) { hook = injectStyles } if (hook) { var functional = options.functional var existing = functional ? options.render : options.beforeCreate if (!functional) { // inject component registration as beforeCreate hook options.beforeCreate = existing ? [].concat(existing, hook) : [hook] } else { // for template-only hot-reload because in that case the render fn doesn't // go through the normalizer options._injectStyles = hook // register for functioal component in vue file options.render = function renderWithStyleInjection (h, context) { hook.call(context) return existing(h, context) } } } return { esModule: esModule, exports: scriptExports, options: options } } /***/ }), /* 4 */ /***/ (function(module, exports) { /** * Truncate HTML string and keep tag safe. * * @method truncate * @param {String} string string needs to be truncated * @param {Number} maxLength length of truncated string * @param {Object} options (optional) * @param {Boolean} [options.keepImageTag] flag to specify if keep image tag, false by default * @param {Boolean} [options.truncateLastWord] truncates last word, true by default * @param {Number} [options.slop] tolerance when options.truncateLastWord is false before we give up and just truncate at the maxLength position, 10 by default (but not greater than maxLength) * @param {Boolean|String} [options.ellipsis] omission symbol for truncated string, '...' by default * @return {String} truncated string */ function truncate(string, maxLength, options) { var EMPTY_OBJECT = {}, EMPTY_STRING = '', DEFAULT_TRUNCATE_SYMBOL = '...', DEFAULT_SLOP = 10 > maxLength ? maxLength : 10, EXCLUDE_TAGS = ['img', 'br'], // non-closed tags items = [], // stack for saving tags total = 0, // record how many characters we traced so far content = EMPTY_STRING, // truncated text storage KEY_VALUE_REGEX = '([\\w|-]+\\s*(=\\s*"[^"]*")?\\s*)*', IS_CLOSE_REGEX = '\\s*\\/?\\s*', CLOSE_REGEX = '\\s*\\/\\s*', SELF_CLOSE_REGEX = new RegExp('<\\/?\\w+\\s*' + KEY_VALUE_REGEX + CLOSE_REGEX + '>'), HTML_TAG_REGEX = new RegExp('<\\/?\\w+\\s*' + KEY_VALUE_REGEX + IS_CLOSE_REGEX + '>'), URL_REGEX = /(((ftp|https?):\/\/)[\-\w@:%_\+.~#?,&\/\/=]+)|((mailto:)?[_.\w\-]+@([\w][\w\-]+\.)+[a-zA-Z]{2,3})/g, // Simple regexp IMAGE_TAG_REGEX = new RegExp('<img\\s*' + KEY_VALUE_REGEX + IS_CLOSE_REGEX + '>'), WORD_BREAK_REGEX = new RegExp('\\W+', 'g'), matches = true, result, index, tail, tag, selfClose; /** * Remove image tag * * @private * @method _removeImageTag * @param {String} string not-yet-processed string * @return {String} string without image tags */ function _removeImageTag(string) { var match = IMAGE_TAG_REGEX.exec(string), index, len; if (!match) { return string; } index = match.index; len = match[0].length; return string.substring(0, index) + string.substring(index + len); } /** * Dump all close tags and append to truncated content while reaching upperbound * * @private * @method _dumpCloseTag * @param {String[]} tags a list of tags which should be closed * @return {String} well-formatted html */ function _dumpCloseTag(tags) { var html = ''; tags.reverse().forEach(function (tag, index) { // dump non-excluded tags only if (-1 === EXCLUDE_TAGS.indexOf(tag)) { html += '</' + tag + '>'; } }); return html; } /** * Process tag string to get pure tag name * * @private * @method _getTag * @param {String} string original html * @return {String} tag name */ function _getTag(string) { var tail = string.indexOf(' '); // TODO: // we have to figure out how to handle non-well-formatted HTML case if (-1 === tail) { tail = string.indexOf('>'); if (-1 === tail) { throw new Error('HTML tag is not well-formed : ' + string); } } return string.substring(1, tail); } /** * Get the end position for String#substring() * * If options.truncateLastWord is FALSE, we try to the end position up to * options.slop characters to avoid breaking in the middle of a word. * * @private * @method _getEndPosition * @param {String} string original html * @param {Number} tailPos (optional) provided to avoid extending the slop into trailing HTML tag * @return {Number} maxLength */ function _getEndPosition (string, tailPos) { var defaultPos = maxLength - total, position = defaultPos, isShort = defaultPos < options.slop, slopPos = isShort ? defaultPos : options.slop - 1, substr, startSlice = isShort ? 0 : defaultPos - options.slop, endSlice = tailPos || (defaultPos + options.slop), result; if (!options.truncateLastWord) { substr = string.slice(startSlice, endSlice); if (tailPos && substr.length <= tailPos) { position = substr.length; } else { while ((result = WORD_BREAK_REGEX.exec(substr)) !== null) { // a natural break position before the hard break position if (result.index < slopPos) { position = defaultPos - (slopPos - result.index); // keep seeking closer to the hard break position // unless a natural break is at position 0 if (result.index === 0 && defaultPos <= 1) break; } // a natural break position exactly at the hard break position else if (result.index === slopPos) { position = defaultPos; break; // seek no more } // a natural break position after the hard break position else { position = defaultPos + (result.index - slopPos); break; // seek no more } } } if (string.charAt(position - 1).match(/\s$/)) position--; } return position; } 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) { matches = HTML_TAG_REGEX.exec(string); if (!matches) { if (total >= maxLength) { break; } matches = URL_REGEX.exec(string); if (!matches || matches.index >= maxLength) { content += string.substring(0, _getEndPosition(string)); break; } 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); } break; } result = matches[0]; index = matches.index; if (total + index > maxLength) { // exceed given `maxLength`, dump everything to clear stack content += string.substring(0, _getEndPosition(string, index)); break; } else { total += index; content += string.substring(0, index); } if ('/' === result[1]) { // move out open tag 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 (string.length > maxLength - total && options.ellipsis) { content += options.ellipsis; } content += _dumpCloseTag(items); if (!options.keepImageTag) { content = _removeImageTag(content); } return content; } module.exports = truncate; /***/ }), /* 5 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; var render = function() { var _vm = this var _h = _vm.$createElement var _c = _vm._self._c || _h return _c("div", [ !_vm.show && !_vm.isHTML ? _c("div", [ _c("span", { class: _vm.textClass }, [ _vm._v("\n " + _vm._s(_vm.truncate(_vm.text)) + "\n ") ]), _vm._v(" "), _vm.showToggle ? _c( "button", { class: _vm.actionClass, attrs: { type: "button" }, on: { click: function($event) { $event.preventDefault() $event.stopPropagation() return _vm.toggle($event) } } }, [_vm._v("\n " + _vm._s(_vm.clamp) + "\n ")] ) : _vm._e() ]) : !_vm.show && _vm.isHTML ? _c("div", [ _c("span", { class: _vm.textClass, domProps: { innerHTML: _vm._s(_vm.truncate(_vm.text)) } }), _vm._v(" "), _vm.showToggle ? _c( "button", { class: _vm.actionClass, attrs: { type: "button" }, on: { click: function($event) { $event.preventDefault() $event.stopPropagation() return _vm.toggle($event) } } }, [_vm._v(_vm._s(_vm.clamp))] ) : _vm._e() ]) : _vm._e(), _vm._v(" "), _vm.show && !_vm.isHTML ? _c("div", [ _c("span", [_vm._v(_vm._s(_vm.text))]), _vm._v(" "), _vm.showToggle ? _c( "button", { class: _vm.actionClass, attrs: { type: "button" }, on: { click: function($event) { $event.preventDefault() $event.stopPropagation() return _vm.toggle($event) } } }, [_vm._v(_vm._s(_vm.less))] ) : _vm._e() ]) : _vm.show && _vm.isHTML ? _c("div", [ _vm.text.length >= _vm.length ? _c("div", { domProps: { innerHTML: _vm._s(_vm.text) } }) : _vm._e(), _vm._v(" "), _vm.showToggle ? _c( "button", { class: _vm.actionClass, attrs: { type: "button" }, on: { click: function($event) { $event.preventDefault() $event.stopPropagation() return _vm.toggle($event) } } }, [_vm._v(_vm._s(_vm.less))] ) : _c("p", [ _vm._v("\n " + _vm._s(_vm.h2p(_vm.text)) + "\n ") ]) ]) : _vm._e() ]) } var staticRenderFns = [] render._withStripped = true var esExports = { render: render, staticRenderFns: staticRenderFns } /* harmony default export */ __webpack_exports__["a"] = (esExports); if (false) { module.hot.accept() if (module.hot.data) { require("vue-hot-reload-api") .rerender("data-v-11b2e33f", esExports) } } /***/ }) /******/ ]); });