diff --git a/README.md b/README.md index 91c2c73..33a8bf6 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ The dotdotdot javascript plugin is licensed under the [CC-BY-NC-4.0 license](htt You can [purchase a license](http://dotdotdot.frebsite.nl#download) if you want to use it in a commercial project. ### Browser support -The dotdotdot javascript plugin uses ES6, meaning IE11 and earlier are not supported. -If you need support for IE11, use the legacy (jQuery) version: [version 3.2.3](https://github.com/FrDH/dotdotdot-JS/releases/tag/v3.2.3). +The dotdotdot javascript plugin uses ES5, meaning IE10 and earlier are not supported. +If you need support for IE10, use the legacy (jQuery) version: [version 3.2.3](https://github.com/FrDH/dotdotdot-JS/releases/tag/v3.2.3). ### Development This project uses [Gulp](http://gulpjs.com/) to minify the JS file. diff --git a/composer.json b/composer.json index 6533b56..4beadc9 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name" : "dotdotdot-js", - "version" : "4.0.1", + "version" : "4.0.2", "authors" : "Fred Heusschen ", "license" : "CC-BY-NC-4.0", "description" : "Dotdotdot is a javascript plugin for truncating multiple line content with an ellipsis.", diff --git a/dist/dotdotdot.js b/dist/dotdotdot.js index 0b3f002..7b1cbf0 100644 --- a/dist/dotdotdot.js +++ b/dist/dotdotdot.js @@ -8,7 +8,7 @@ } }(this, function() { /*! - * dotdotdot JS 4.0.1 + * dotdotdot JS 4.0.2 * * dotdotdot.frebsite.nl * @@ -18,6 +18,6 @@ * License: CC-BY-NC-4.0 * http://creativecommons.org/licenses/by-nc/4.0/ */ -class Dotdotdot{constructor(t,e){this.container=t,this.options=e,this.watchTimeout=null,this.watchInterval=null,this.resizeEvent=null;for(let t in Dotdotdot.options)Dotdotdot.options.hasOwnProperty(t)&&void 0===this.options[t]&&(this.options[t]=Dotdotdot.options[t]);var i=this.container.dotdotdot;i&&i.destroy(),this.API={},["truncate","restore","destroy","watch","unwatch"].forEach(t=>{this.API[t]=(()=>this[t].call(this))}),this.container.dotdotdot=this.API,this.originalStyle=this.container.getAttribute("style")||"",this.originalContent=this._getOriginalContent(),this.ellipsis=document.createTextNode(this.options.ellipsis),this._setStyles(),null===this.options.height&&(this.options.height=this._getMaxHeight()),this.truncate(),this.options.watch&&this.watch()}restore(){this.unwatch(),this.container.setAttribute("style",this.originalStyle),this.container.classList.remove("ddd-truncated"),this.container.innerHTML="",this.originalContent.forEach(t=>{this.container.append(t)})}destroy(){this.restore(),this.container.dotdotdot=null}watch(){this.unwatch();var t={width:null,height:null},e=(e,i,o)=>{if(this.container.offsetWidth||this.container.offsetHeight||this.container.getClientRects().length){let n={width:e[i],height:e[o]};return t.width==n.width&&t.height==n.height||this.truncate(),n}return t};"window"==this.options.watch?(this.resizeEvent=(i=>{this.watchTimeout&&clearTimeout(this.watchTimeout),this.watchTimeout=setTimeout(()=>{t=e(window,"innerWidth","innerHeight")},100)}),window.addEventListener("resize",this.resizeEvent)):this.watchInterval=setInterval(()=>{t=e(this.container,"clientWidth","clientHeight")},1e3)}unwatch(){this.resizeEvent&&(window.removeEventListener("resize",this.resizeEvent),this.resizeEvent=null),this.watchInterval&&clearInterval(this.watchInterval),this.watchTimeout&&clearTimeout(this.watchTimeout)}truncate(){this.container.innerHTML="",this.originalContent.forEach(t=>{this.container.append(t.cloneNode(!0))}),this.maxHeight=this._getMaxHeight();var t=!1;return this._fits()||(t=!0,this._truncateToNode(this.container)),this.container.classList[t?"add":"remove"]("ddd-truncated"),this.options.callback.call(this.container,t),t}_truncateToNode(t){var e=[],i=[];if(Dotdotdot.$.contents(t).forEach(t=>{if(1!=t.nodeType||!t.matches(".ddd-keep")){let o=document.createComment("");t.replaceWith(o),i.push(t),e.push(o)}}),i.length){for(var o=0;o1)return void i[o-2].remove();break}}for(var n=o;n=0;s--)if(n=o.slice(0,s).join(i),t.textContent=this._addEllipsis(n),this._fits()){"letter"==this.options.truncate&&(t.textContent=o.slice(0,s+1).join(i),this._truncateToLetter(t));break}}_truncateToLetter(t){for(var e=t.textContent.split(""),i="",o=e.length;o>=0&&(!(i=e.slice(0,o).join("")).length||(t.textContent=this._addEllipsis(i),!this._fits()));o--);}_fits(){return console.log(this.container.scrollHeight),this.container.scrollHeight<=this.maxHeight+this.options.tolerance}_addEllipsis(t){for(var e=[" "," ",",",";",".","!","?"];e.indexOf(t.slice(-1))>-1;)t=t.slice(0,-1);return t+=this.ellipsis.textContent}_setStyles(){var t=window.getComputedStyle(this.container);"break-word"!==t["word-wrap"]&&(this.container.style["word-wrap"]="break-word"),"nowrap"===t["white-space"]&&(this.container.style["white-space"]="normal")}_getOriginalContent(){let t="script, style";this.options.keep&&(t+=", "+this.options.keep),Dotdotdot.$.find(t,this.container).forEach(t=>{t.classList.add("ddd-keep")}),[this.container,...Dotdotdot.$.find("*",this.container)].forEach(t=>{t.normalize(),Dotdotdot.$.contents(t).forEach(e=>{let i=!1;if(3==e.nodeType){if(""==e.textContent.trim()){let t=e.previousSibling,o=e.nextSibling;e.parentElement.matches("table, thead, tbody, tfoot, tr, dl, ul, ol, video")?i=!0:!t||t.matches("div, p, table, td, td, dt, dd, li")?i=!0:o&&!o.matches("div, p, table, td, td, dt, dd, li")||(i=!0)}}else 8==e.nodeType&&(i=!0);i&&t.removeChild(e)})});let e=[];return Dotdotdot.$.contents(this.container).forEach(t=>{e.push(t.cloneNode(!0))}),e}_getMaxHeight(){if("number"==typeof this.options.height)return this.options.height;for(var t=window.getComputedStyle(this.container),e=["maxHeight","height"],i=0,o=0;o(e=e||document,Array.prototype.slice.call(e.querySelectorAll(t))),contents:t=>(t=t||document,Array.prototype.slice.call(t.childNodes))},function(t){void 0!==t&&(t.fn.dotdotdot=function(t){return this.each((e,i)=>{let o=new Dotdotdot(i,t);i.dotdotdot=o.API})})}(Zepto||jQuery); +var Dotdotdot=function(){function t(e,i){var n=this;for(var o in this.container=e,this.options=i,this.watchTimeout=null,this.watchInterval=null,this.resizeEvent=null,t.options)t.options.hasOwnProperty(o)&&void 0===this.options[o]&&(this.options[o]=t.options[o]);var r=this.container.dotdotdot;r&&r.destroy(),this.API={},["truncate","restore","destroy","watch","unwatch"].forEach(function(t){n.API[t]=function(){return n[t].call(n)}}),this.container.dotdotdot=this.API,this.originalStyle=this.container.getAttribute("style")||"",this.originalContent=this._getOriginalContent(),this.ellipsis=document.createTextNode(this.options.ellipsis);var s=window.getComputedStyle(this.container);"break-word"!==s["word-wrap"]&&(this.container.style["word-wrap"]="break-word"),"nowrap"===s["white-space"]&&(this.container.style["white-space"]="normal"),null===this.options.height&&(this.options.height=this._getMaxHeight()),this.truncate(),this.options.watch&&this.watch()}return t.prototype.restore=function(){var t=this;this.unwatch(),this.container.setAttribute("style",this.originalStyle),this.container.classList.remove("ddd-truncated"),this.container.innerHTML="",this.originalContent.forEach(function(e){t.container.append(e)})},t.prototype.destroy=function(){this.restore(),this.container.dotdotdot=null},t.prototype.watch=function(){var t=this;this.unwatch();var e={width:null,height:null},i=function(i,n,o){if(t.container.offsetWidth||t.container.offsetHeight||t.container.getClientRects().length){var r={width:i[n],height:i[o]};return e.width==r.width&&e.height==r.height||t.truncate(),r}return e};"window"==this.options.watch?(this.resizeEvent=function(n){t.watchTimeout&&clearTimeout(t.watchTimeout),t.watchTimeout=setTimeout(function(){e=i(window,"innerWidth","innerHeight")},100)},window.addEventListener("resize",this.resizeEvent)):this.watchInterval=setInterval(function(){e=i(t.container,"clientWidth","clientHeight")},1e3)},t.prototype.unwatch=function(){this.resizeEvent&&(window.removeEventListener("resize",this.resizeEvent),this.resizeEvent=null),this.watchInterval&&clearInterval(this.watchInterval),this.watchTimeout&&clearTimeout(this.watchTimeout)},t.prototype.truncate=function(){var t=this,e=!1;return this.container.innerHTML="",this.originalContent.forEach(function(e){t.container.append(e.cloneNode(!0))}),this.maxHeight=this._getMaxHeight(),this._fits()||(e=!0,this._truncateToNode(this.container)),this.container.classList[e?"add":"remove"]("ddd-truncated"),this.options.callback.call(this.container,e),e},t.prototype._truncateToNode=function(e){var i=[],n=[];if(t.$.contents(e).forEach(function(t){if(1!=t.nodeType||!t.matches(".ddd-keep")){var e=document.createComment("");t.replaceWith(e),n.push(t),i.push(e)}}),n.length){for(var o=0;o1)return void n[o-2].remove();break}}for(var a=o;a=0;o--)if(t.textContent=this._addEllipsis(n.slice(0,o).join(i)),this._fits()){"letter"==this.options.truncate&&(t.textContent=n.slice(0,o+1).join(i),this._truncateToLetter(t));break}},t.prototype._truncateToLetter=function(t){for(var e=t.textContent.split(""),i="",n=e.length;n>=0&&(!(i=e.slice(0,n).join("")).length||(t.textContent=this._addEllipsis(i),!this._fits()));n--);},t.prototype._fits=function(){return this.container.scrollHeight<=this.maxHeight+this.options.tolerance},t.prototype._addEllipsis=function(t){for(var e=[" "," ",",",";",".","!","?"];e.indexOf(t.slice(-1))>-1;)t=t.slice(0,-1);return t+=this.ellipsis.textContent},t.prototype._getOriginalContent=function(){var e="script, style";this.options.keep&&(e+=", "+this.options.keep),t.$.find(e,this.container).forEach(function(t){t.classList.add("ddd-keep")}),[this.container].concat(t.$.find("*",this.container)).forEach(function(e){e.normalize(),t.$.contents(e).forEach(function(t){var i=!1;if(3==t.nodeType){if(""==t.textContent.trim()){var n=t.previousSibling,o=t.nextSibling;t.parentElement.matches("table, thead, tbody, tfoot, tr, dl, ul, ol, video")?i=!0:!n||n.matches("div, p, table, td, td, dt, dd, li")?i=!0:o&&!o.matches("div, p, table, td, td, dt, dd, li")||(i=!0)}}else 8==t.nodeType&&(i=!0);i&&e.removeChild(t)})});var i=[];return t.$.contents(this.container).forEach(function(t){i.push(t.cloneNode(!0))}),i},t.prototype._getMaxHeight=function(){if("number"==typeof this.options.height)return this.options.height;for(var t=window.getComputedStyle(this.container),e=["maxHeight","height"],i=0,n=0;n { - return gulp.src( 'src/dotdotdot.ts' ) + return gulp.src( 'src/*.ts' ) .pipe( typescript({ - "target": "es6" + "target": "es5" }) ) .pipe( terser({ output: { @@ -25,9 +25,7 @@ exports.default = js; // Watch task 'gulp watch': Starts a watch on JS tasks const watch = ( cb ) => { - gulp.watch( 'src/*.ts', function( cb ) { - js(); - cb(); - }); + gulp.watch( 'src/*.ts', js ); + cb(); }; exports.watch = watch; \ No newline at end of file diff --git a/index.html b/index.html index 9cf28d0..3dcb1b3 100644 --- a/index.html +++ b/index.html @@ -163,7 +163,6 @@

Toggle full story

*/ $('#xmpl-2') .find( 'li' ) - .first() .each( function() { @@ -180,6 +179,9 @@

Toggle full story

// Add a slash before the ellipsis ellipsis: '/\u2026', + // Adjustment for the top- and bottom padding. + tolerance: 20, + // Prevents the from being removed keep: '.file' }); diff --git a/package-lock.json b/package-lock.json index 6d803ae..435fd2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "dotdotdot-js", - "version": "4.0.1", + "version": "4.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a2c2ca3..5f9b7df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dotdotdot-js", - "version": "4.0.1", + "version": "4.0.2", "main": "dist/dotdotdot.js", "author": "Fred Heusschen ", "license": "CC-BY-NC-4.0", diff --git a/src/dotdotdot.ts b/src/dotdotdot.ts index 632bf24..36d382f 100644 --- a/src/dotdotdot.ts +++ b/src/dotdotdot.ts @@ -1,5 +1,5 @@ /*! - * dotdotdot JS 4.0.1 + * dotdotdot JS 4.0.2 * * dotdotdot.frebsite.nl * @@ -10,37 +10,6 @@ * http://creativecommons.org/licenses/by-nc/4.0/ */ - - /** An object with any value. */ -interface dddLooseObject { - [key: string] : any -} - -/** Default options for the class. */ -interface dddOptions { - - /** The ellipsis to place after the truncated text. */ - ellipsis ?: string, - - /** Function to invoke after the truncate process. */ - callback ?: Function, - - /** How to truncate: 'node', 'word' (default) or 'letter'. */ - truncate ?: string, - - /** Optional tolerance for the container height. */ - tolerance ?: number, - - /** Selector for elements not to remove from the DOM. */ - keep ?: string, - - /** Whether and when to update the ellipsis: null, 'window' (default) or 'resize' */ - watch ?: string, - - /** The height for the container. If null, the max-height will be read from the CSS properties. */ - height ?: number -} - /** * Class for a multiline ellipsis. @@ -48,7 +17,7 @@ interface dddOptions { class Dotdotdot { /** Plugin version. */ - static version : string = '4.0.0' + static version : string = '4.0.2' /** Default options. */ @@ -79,7 +48,7 @@ class Dotdotdot { ellipsis : Text /** The API */ - API : dddLooseObject + API : dddFunctionObject /** Storage for the watch timeout, oddly it has a number type. */ watchTimeout : number @@ -162,7 +131,15 @@ class Dotdotdot { this.ellipsis = document.createTextNode( this.options.ellipsis ); // Set CSS properties for the container. - this._setStyles(); + var computedStyle = window.getComputedStyle( this.container ); + if ( computedStyle[ 'word-wrap' ] !== 'break-word' ) + { + this.container.style[ 'word-wrap' ] = 'break-word'; + } + if ( computedStyle[ 'white-space' ] === 'nowrap' ) + { + this.container.style[ 'white-space' ] = 'normal'; + } // Set the max-height for the container. if ( this.options.height === null ) @@ -305,10 +282,12 @@ class Dotdotdot { } /** - * Truncate the container. + * Start the truncate process. */ truncate() { + var isTruncated = false; + // Fill the container with all the original content. this.container.innerHTML = ''; this.originalContent.forEach(( element ) => { @@ -319,7 +298,6 @@ class Dotdotdot { this.maxHeight = this._getMaxHeight(); // Truncate the text. - var isTruncated = false; if ( !this._fits() ) { isTruncated = true; @@ -335,13 +313,19 @@ class Dotdotdot { return isTruncated; } - _truncateToNode( element ) - { + /** + * Truncate an element by removing elements from the end. + * + * @param {HTMLElement} element The element to truncate. + */ + _truncateToNode( + element : HTMLElement + ) { var _coms = [], _elms = []; - // Empty the node + // Empty the element // -> replace all contents with comments Dotdotdot.$.contents( element ) .forEach(( element ) => { @@ -361,8 +345,8 @@ class Dotdotdot { return; } - // Re-fill the node - // -> replace comments with contents until it doesn't fit anymore + // Re-fill the element + // -> replace comments with contents until it doesn't fit anymore. for ( var e = 0; e < _elms.length; e++ ) { @@ -379,13 +363,10 @@ class Dotdotdot { case 3: _elms[ e ].after( ellipsis ); break; - - default: - console.log( _elms[e], _elms[e].nodeType) } let fits = this._fits(); - ellipsis.parentElement.removeChild(ellipsis); + ellipsis.parentElement.removeChild( ellipsis ); if ( !fits ) { @@ -398,14 +379,14 @@ class Dotdotdot { } } - // Remove left over comments + // Remove left over comments. for ( var c = e; c < _coms.length; c++ ) { _coms[ c ].remove(); } - // Get last node - // -> the node that overflows + // Get last element + // -> the element that overflows. var _last = _elms[ Math.max( 0, Math.min( e, _elms.length - 1 ) ) ]; @@ -414,20 +395,20 @@ class Dotdotdot { if ( _last.nodeType == 1 ) { - var element = document.createElement( _last.nodeName ); + let element = document.createElement( _last.nodeName ); element.append( this.ellipsis ); _last.replaceWith( element ); // ... fits - // -> Restore the full last node + // -> Restore the full last element. if ( this._fits() ) { element.replaceWith( _last ); } // ... doesn't fit - // -> remove it and go back one node + // -> remove it and go back one element. else { element.remove(); @@ -435,7 +416,7 @@ class Dotdotdot { } } - // Proceed inside last node + // Proceed inside last element. if ( _last.nodeType == 1 ) { this._truncateToNode( _last ); @@ -446,24 +427,27 @@ class Dotdotdot { } } - _truncateToWord( element ) - { + /** + * Truncate a sentence by removing words from the end. + * + * @param {HTMLElement} element The element to truncate. + */ + _truncateToWord( + element : HTMLElement + ) { var text = element.textContent, seporator = ( text.indexOf( ' ' ) !== -1 ) ? ' ' : '\u3000', - parts = text.split( seporator ), - content = ''; + words = text.split( seporator ); - for ( var a = parts.length; a >= 0; a-- ) + for ( var a = words.length; a >= 0; a-- ) { - content = parts.slice( 0, a ).join( seporator ); - - element.textContent = this._addEllipsis( content ); + element.textContent = this._addEllipsis( words.slice( 0, a ).join( seporator ) ); if ( this._fits() ) { if ( this.options.truncate == 'letter' ) { - element.textContent = parts.slice( 0, a + 1 ).join( seporator ); + element.textContent = words.slice( 0, a + 1 ).join( seporator ); this._truncateToLetter( element ); } break; @@ -471,23 +455,28 @@ class Dotdotdot { } } - _truncateToLetter( element ) - { + /** + * Truncate a word by removing letters from the end. + * + * @param {HTMLElement} element The element to truncate. + */ + _truncateToLetter( + element : HTMLElement + ) { - var txt = element.textContent, - arr = txt.split( '' ), - str = ''; + var letters = element.textContent.split( '' ), + text = ''; - for ( var a = arr.length; a >= 0; a-- ) + for ( var a = letters.length; a >= 0; a-- ) { - str = arr.slice( 0, a ).join( '' ); + text = letters.slice( 0, a ).join( '' ); - if ( !str.length ) + if ( !text.length ) { continue; } - element.textContent = this._addEllipsis( str ); + element.textContent = this._addEllipsis( text ); if ( this._fits() ) { @@ -496,14 +485,25 @@ class Dotdotdot { } } - _fits() + /** + * Test if the content fits in the container. + * + * @return {boolean} Whether or not the content fits in the container. + */ + _fits() : boolean { - console.log( this.container.scrollHeight ) return ( this.container.scrollHeight <= this.maxHeight + this.options.tolerance ); } - _addEllipsis( text ) - { + /** + * Add the ellipsis to a text. + * + * @param {string} text The text to add the ellipsis to. + * @return {string} The text with the added ellipsis. + */ + _addEllipsis( + text : string + ) : string { var remove = [' ', '\u3000', ',', ';', '.', '!', '?']; while ( remove.indexOf( text.slice( -1 ) ) > -1 ) @@ -515,26 +515,12 @@ class Dotdotdot { return text; } - /** - * Set CSS properties for the container. - */ - _setStyles() { - - var computedStyle = window.getComputedStyle( this.container ); - if ( computedStyle[ 'word-wrap' ] !== 'break-word' ) - { - this.container.style[ 'word-wrap' ] = 'break-word'; - } - if ( computedStyle[ 'white-space' ] === 'nowrap' ) - { - this.container.style[ 'white-space' ] = 'normal'; - } - } - /** * Sanitize and collect the original contents. + * + * @return {array} The sanitizes HTML elements. */ - _getOriginalContent() { + _getOriginalContent() : HTMLElement[] { let keep = 'script, style'; if ( this.options.keep ) @@ -609,7 +595,12 @@ class Dotdotdot { return content; } - _getMaxHeight() + /** + * Find the max-height for the container. + * + * @return {number} The max-height for the container. + */ + _getMaxHeight() : number { if ( typeof this.options.height == 'number' ) { @@ -659,7 +650,16 @@ class Dotdotdot { return Math.max( height, 0 ); } + /** DOM traversing functions to uniform datatypes. */ static $ = { + + /** + * Find elements by a query selector in an element. + * + * @param {string} selector The selector to search for. + * @param {HTMLElement} [element=document] The element to search in. + * @return {array} The found elements. + */ find: ( selector : string, element ?: HTMLElement | Document @@ -668,6 +668,12 @@ class Dotdotdot { return Array.prototype.slice.call( element.querySelectorAll( selector ) ); }, + /** + * Collect child nodes (HTML elements and TextNodes) in an element. + * + * @param {HTMLElement} [element=document] The element to search in. + * @return {array} The found nodes. + */ contents: ( element ?: HTMLElement | Document ) : Node[] => { @@ -677,6 +683,7 @@ class Dotdotdot { } } +// The jQuery plugin. declare var Zepto : any declare var jQuery : any @@ -690,5 +697,5 @@ declare var jQuery : any }); } } -})( Zepto || jQuery ); +})( document[ 'Zepto' ] || document[ 'jQuery' ] ); diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..9e3f59d --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,34 @@ + /** An object with any value. */ +interface dddLooseObject { + [key: string] : any +} + + /** An object with function values. */ +interface dddFunctionObject { + [key: string] : Function +} + +/** Default options for the class. */ +interface dddOptions { + + /** The ellipsis to place after the truncated text. */ + ellipsis ?: string, + + /** Function to invoke after the truncate process. */ + callback ?: Function, + + /** How to truncate: 'node', 'word' (default) or 'letter'. */ + truncate ?: string, + + /** Optional tolerance for the container height. */ + tolerance ?: number, + + /** Selector for elements not to remove from the DOM. */ + keep ?: string, + + /** Whether and when to update the ellipsis: null, 'window' (default) or 'resize' */ + watch ?: string, + + /** The height for the container. If null, the max-height will be read from the CSS properties. */ + height ?: number +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c05375e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "include": [ + "src/*.ts" + ], + "compilerOptions": { + "target": "es5" + } +} \ No newline at end of file