From 8027d5636087817cd283e0448ac4911737390bbb Mon Sep 17 00:00:00 2001 From: DEVSK Date: Tue, 1 Mar 2022 15:16:24 +0100 Subject: [PATCH] update readme, adjust license error, bump version --- README.md | 8 ++++---- bower.json | 2 +- dist/cuttr.js | 10 ++++++++-- dist/cuttr.min.js | 4 ++-- dist/cuttr.min.js.map | 2 +- examples/index.html | 2 -- package.json | 2 +- src/cuttr.js | 12 ++++++++++-- 8 files changed, 27 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ec60c35..bb248fc 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ License - - Known Vulnerabilities + + Known Vulnerabilities License @@ -56,9 +56,9 @@ Link directly to Cuttr files on [unpkg](https://unpkg.com/cuttr). Link directly to Cuttr files on [cdnjs](https://cdnjs.com/libraries/cuttr). ``` html - + - + ``` diff --git a/bower.json b/bower.json index 7d90e0b..e68d2e3 100644 --- a/bower.json +++ b/bower.json @@ -2,7 +2,7 @@ "name": "cuttr", "description": "Cuttr is a javascript plugin that truncates multi-line string content with multiple truncation methods and custom ellipsis.", "main": "dist/cuttr.js", - "version": "1.4.0", + "version": "1.4.1", "authors": [ "DEVSK" ], diff --git a/dist/cuttr.js b/dist/cuttr.js index dbb4cc8..7dbc2ae 100644 --- a/dist/cuttr.js +++ b/dist/cuttr.js @@ -1,5 +1,5 @@ /*! - * Cuttr 1.4.0 + * Cuttr 1.4.1 * https://github.com/d-e-v-s-k/cuttr-js * * @license GPLv3 for open source use only @@ -22,6 +22,9 @@ root.Cuttr = factory(); } })(this, function () { + // private global vars + var CUTTR_LICENSE = true; // init Cuttr + var Cuttr = function Cuttr(el, options) { 'use strict'; @@ -314,7 +317,10 @@ function displayWarnings(isAuthorized) { - if (!isAuthorized) { + if (!isAuthorized && CUTTR_LICENSE) { + // declare global const to show error only once + CUTTR_LICENSE = false; // show error message + showError('error', 'Cuttr.js has a GPLv3 license and it requires a `licenseKey` option. Read about it here:'); showError('error', 'https://github.com/d-e-v-s-k/cuttr-js#options'); } diff --git a/dist/cuttr.min.js b/dist/cuttr.min.js index 05c2fa3..bf0cbcb 100644 --- a/dist/cuttr.min.js +++ b/dist/cuttr.min.js @@ -1,5 +1,5 @@ /*! - * Cuttr 1.4.0 + * Cuttr 1.4.1 * https://github.com/d-e-v-s-k/cuttr-js * * @license GPLv3 for open source use only @@ -8,5 +8,5 @@ * * Copyright (C) 2022 https://cuttr.kulahs.de/ - A project by DEVSK **/ -!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():t.Cuttr=e()}(this,function(){return function t(e,n){"use strict";var T=Object.create(t.prototype);T.options={elementsToTruncate:"string"==typeof e?document.querySelectorAll(e):e,originalContent:[],contentVisibilityState:[],contentTruncationState:[],truncate:"characters",length:100,ending:"...",loadedClass:"cuttr--loaded",title:!1,readMore:!1,readMoreText:"read more",readLessText:"read less",readMoreBtnPosition:"after",readMoreBtnTag:"button",readMoreBtnSelectorClass:"cuttr__readmore",readMoreBtnAdditionalClasses:"",afterTruncate:function(){},afterExpand:function(){},dataIndex:"data-cuttr-index"},n&&Object.keys(n).forEach(function(t){T.options[t]=n[t]});function f(t,e,n,o){var i=t.dataset.cuttrIndex;switch(null==n&&(n=100),null==o&&(o="..."),t.dataset.cuttrMethod?t.dataset.cuttrMethod:T.options.truncate){case"characters":return e.length>n?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,e.substring(0,n-o.length)+o+" "):e;case"words":var a=e.split(/ (?=[^>]*(?:<|$))/);return a.length>n?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,a.splice(0,n).join(" ")+" "+o+" "):e;case"sentences":var r=e.match(/[^\.!\?]+[\.!\?]+/g);return r.length>n?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,r.splice(0,n).join(" ")+" "+o+" "):e;default:return e.length>n?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,e.substring(0,n-o.length)+o):e}}function m(t,e){var n,o=t,i=o.dataset.cuttrIndex,a=o.dataset.cuttrReadmore?o.dataset.cuttrReadmore:T.options.readMoreText,r=o.dataset.cuttrReadmore?o.dataset.cuttrReadless:T.options.readLessText,s=o.dataset.cuttrReadmorePosition?o.dataset.cuttrReadmorePosition:T.options.readMoreBtnPosition,d=o.dataset.cuttrReadmoreTag?o.dataset.cuttrReadmoreTag:T.options.readMoreBtnTag,c="."+T.options.readMoreBtnSelectorClass,l=o.dataset.cuttrReadmoreAdditionalClasses?o.dataset.cuttrReadmoreAdditionalClasses:T.options.readMoreBtnAdditionalClasses,u=T.options.contentVisibilityState[i]?r:a,p=" <"+d+' aria-expanded="'+(T.options.contentVisibilityState[i]?"true":"false")+'" class="'+T.options.readMoreBtnSelectorClass+" "+l+'">'+u.replace(/<[^>]*>/g,"")+"";if("after"==s&&o.nextElementSibling?n=o.nextElementSibling.matches(c):"inside"==s&&(n=o.querySelector(c)),!n){switch(s){case"after":o.insertAdjacentHTML("afterend",p);break;case"inside":o.insertAdjacentHTML("beforeend",p);break;default:console.log("no matching read-more button position defined")}e||("after"==s?o.nextElementSibling.addEventListener("click",function(t){t.target&&t.target.classList.contains(T.options.readMoreBtnSelectorClass)&&g(t,s)}):"inside"==s&&o.addEventListener("click",function(t){t.target&&t.target.classList.contains(T.options.readMoreBtnSelectorClass)&&g(t,s)}))}}function g(t,e){var n,o="after"==e?t.target.previousElementSibling:t.target.parentNode,i=o.innerHTML,a=o.dataset.cuttrIndex,r=o.dataset.cuttrReadmore?o.dataset.cuttrReadmore:T.options.readMoreText,s=o.dataset.cuttrReadmore?o.dataset.cuttrReadless:T.options.readLessText,d=o.dataset.cuttrLength?o.dataset.cuttrLength:T.options.length,c=o.dataset.cuttrEnding?o.dataset.cuttrEnding:T.options.ending;T.options.contentVisibilityState[a]?(n=f(o,i.trim(),d,c),o.innerHTML=n,T.options.contentVisibilityState[a]=!1,"inside"==e&&T.options.readMore&&m(o,!0),t.target.innerHTML=r.replace(/<[^>]*>/g,""),T.options.afterTruncate.call(o)):(o.innerHTML=T.options.originalContent[a],T.options.contentVisibilityState[a]=!0,"inside"==e&&T.options.readMore&&m(o,!0),t.target.innerHTML=s.replace(/<[^>]*>/g,""),T.options.afterExpand.call(o))}function d(t,e){window.console&&window.console[t]&&window.console[t]("Cuttr: "+e)}return T.expandContent=function(t,e){for(var n=t?document.querySelectorAll(t):T.options.elementsToTruncate,o=0;o]*>/g,""))),T.options.afterExpand.call(i))}},T.truncateContent=function(t,e){for(var n=t?document.querySelectorAll(t):T.options.elementsToTruncate,o=0;o]*>/g,""))),T.options.afterTruncate.call(a))}},T.destroy=function(t,e){var n;T.expandContent(t,e),n=t?document.querySelectorAll(t):T.options.elementsToTruncate;for(var o=0;on?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,e.substring(0,n-o.length)+o+" "):e;case"words":var a=e.split(/ (?=[^>]*(?:<|$))/);return a.length>n?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,a.splice(0,n).join(" ")+" "+o+" "):e;case"sentences":var r=e.match(/[^\.!\?]+[\.!\?]+/g);return r.length>n?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,r.splice(0,n).join(" ")+" "+o+" "):e;default:return e.length>n?(T.options.contentTruncationState[i]=!0,T.options.contentVisibilityState[i]=!1,e.substring(0,n-o.length)+o):e}}function m(t,e){var n,o=t,i=o.dataset.cuttrIndex,a=o.dataset.cuttrReadmore?o.dataset.cuttrReadmore:T.options.readMoreText,r=o.dataset.cuttrReadmore?o.dataset.cuttrReadless:T.options.readLessText,s=o.dataset.cuttrReadmorePosition?o.dataset.cuttrReadmorePosition:T.options.readMoreBtnPosition,d=o.dataset.cuttrReadmoreTag?o.dataset.cuttrReadmoreTag:T.options.readMoreBtnTag,c="."+T.options.readMoreBtnSelectorClass,l=o.dataset.cuttrReadmoreAdditionalClasses?o.dataset.cuttrReadmoreAdditionalClasses:T.options.readMoreBtnAdditionalClasses,u=T.options.contentVisibilityState[i]?r:a,p=" <"+d+' aria-expanded="'+(T.options.contentVisibilityState[i]?"true":"false")+'" class="'+T.options.readMoreBtnSelectorClass+" "+l+'">'+u.replace(/<[^>]*>/g,"")+"";if("after"==s&&o.nextElementSibling?n=o.nextElementSibling.matches(c):"inside"==s&&(n=o.querySelector(c)),!n){switch(s){case"after":o.insertAdjacentHTML("afterend",p);break;case"inside":o.insertAdjacentHTML("beforeend",p);break;default:console.log("no matching read-more button position defined")}e||("after"==s?o.nextElementSibling.addEventListener("click",function(t){t.target&&t.target.classList.contains(T.options.readMoreBtnSelectorClass)&&g(t,s)}):"inside"==s&&o.addEventListener("click",function(t){t.target&&t.target.classList.contains(T.options.readMoreBtnSelectorClass)&&g(t,s)}))}}function g(t,e){var n,o="after"==e?t.target.previousElementSibling:t.target.parentNode,i=o.innerHTML,a=o.dataset.cuttrIndex,r=o.dataset.cuttrReadmore?o.dataset.cuttrReadmore:T.options.readMoreText,s=o.dataset.cuttrReadmore?o.dataset.cuttrReadless:T.options.readLessText,d=o.dataset.cuttrLength?o.dataset.cuttrLength:T.options.length,c=o.dataset.cuttrEnding?o.dataset.cuttrEnding:T.options.ending;T.options.contentVisibilityState[a]?(n=f(o,i.trim(),d,c),o.innerHTML=n,T.options.contentVisibilityState[a]=!1,"inside"==e&&T.options.readMore&&m(o,!0),t.target.innerHTML=r.replace(/<[^>]*>/g,""),T.options.afterTruncate.call(o)):(o.innerHTML=T.options.originalContent[a],T.options.contentVisibilityState[a]=!0,"inside"==e&&T.options.readMore&&m(o,!0),t.target.innerHTML=s.replace(/<[^>]*>/g,""),T.options.afterExpand.call(o))}function d(t,e){window.console&&window.console[t]&&window.console[t]("Cuttr: "+e)}return T.expandContent=function(t,e){for(var n=t?document.querySelectorAll(t):T.options.elementsToTruncate,o=0;o]*>/g,""))),T.options.afterExpand.call(i))}},T.truncateContent=function(t,e){for(var n=t?document.querySelectorAll(t):T.options.elementsToTruncate,o=0;o]*>/g,""))),T.options.afterTruncate.call(a))}},T.destroy=function(t,e){var n;T.expandContent(t,e),n=t?document.querySelectorAll(t):T.options.elementsToTruncate;for(var o=0;o -1;\n\n // return if no target element defined\n if (!self.options.elementsToTruncate) {\n return;\n } else {\n displayWarnings(isAuthorized);\n }\n\n // set element type depending on source\n if ( !('length' in self.options.elementsToTruncate) )\n self.options.elementsToTruncate = [self.options.elementsToTruncate];\n\n // loop through target elements to truncate\n for (let i = 0; i < self.options.elementsToTruncate.length; i++) {\n\n const currentElement = self.options.elementsToTruncate[i];\n const currentContent = currentElement.innerHTML;\n const truncateLength = (currentElement.dataset.cuttrLength) ? currentElement.dataset.cuttrLength : self.options.length;\n const truncateEnding = (currentElement.dataset.cuttrEnding) ? currentElement.dataset.cuttrEnding : self.options.ending;\n const contentToTitle = (currentElement.dataset.cuttrTitle) ? currentElement.dataset.cuttrTitle : self.options.title;\n let truncatedContent;\n\n // add truncate-element index to element\n currentElement.setAttribute(self.options.dataIndex, i);\n\n // temporary save elements original content\n self.options.originalContent.push(currentContent);\n\n // truncate content\n truncatedContent = truncateIt(currentElement, currentContent.trim(), truncateLength, truncateEnding);\n\n // set title attr with original text content\n if (contentToTitle)\n currentElement.title = currentElement.textContent.trim();\n\n // set new content\n currentElement.innerHTML = truncatedContent;\n\n // add read-more button if current content is truncated\n if (self.options.contentTruncationState[i]) {\n\n if (self.options.readMore)\n addReadMore(currentElement);\n\n currentElement.classList += ' ' + self.options.loadedClass;\n\n }\n\n // here go the callbacks\n self.options.afterTruncate.call(currentElement);\n\n }\n\n }\n\n\n /*\n truncate text to specific length\n */\n function truncateIt(thisElement, str, length, ending) {\n\n const thisIndex = thisElement.dataset.cuttrIndex;\n const truncateMethod = (thisElement.dataset.cuttrMethod) ? thisElement.dataset.cuttrMethod : self.options.truncate;\n\n // set defaults\n if (length == null) {\n length = 100;\n }\n\n // set defaults\n if (ending == null) {\n ending = '...';\n }\n\n // truncate content based on method\n switch (truncateMethod) {\n\n // truncate characters only\n case 'characters':\n\n // check if content (string) is longer than truncation limit\n if (str.length > length) {\n\n // set current content truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n\n // return new string\n return str.substring(0, length - ending.length) + ending + ' ';\n\n } else {\n return str;\n }\n\n break;\n\n // truncate words\n case 'words':\n\n const words = str.split(/ (?=[^>]*(?:<|$))/);\n\n // check if content (string) is longer than truncation limit\n if (words.length > length) {\n\n // set current content truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n // return new string\n // split spaces followed by sequence of characters are NOT greater-than signs, less-than sign\n return words.splice(0,length).join(' ') + ' ' + ending + ' ';\n\n } else {\n return str;\n }\n\n break;\n\n // truncate full sentences\n case 'sentences':\n\n const sentences = str.match(/[^\\.!\\?]+[\\.!\\?]+/g);\n\n // check if content (string) is longer than truncation limit\n if (sentences.length > length) {\n\n // set current contetn truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n // return new string\n return sentences.splice(0,length).join(' ') + ' ' + ending + ' ';\n\n } else {\n return str;\n }\n\n break;\n\n // truncate characters by default\n default:\n\n // check if content (string) is longer than truncation limit\n if (str.length > length) {\n\n // set current contetn truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n // return new string\n return str.substring(0, length - ending.length) + ending;\n\n } else {\n return str;\n }\n\n }\n\n }\n\n\n /*\n append read more button\n */\n function addReadMore(thisElement, updated) {\n\n const currentElement = thisElement;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readMoreText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadmore : self.options.readMoreText;\n const readLessText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadless : self.options.readLessText;\n const btnPosition = (currentElement.dataset.cuttrReadmorePosition) ? currentElement.dataset.cuttrReadmorePosition : self.options.readMoreBtnPosition;\n const btnTag = (currentElement.dataset.cuttrReadmoreTag) ? currentElement.dataset.cuttrReadmoreTag : self.options.readMoreBtnTag;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n const btnAdditionalClasses = (currentElement.dataset.cuttrReadmoreAdditionalClasses) ? currentElement.dataset.cuttrReadmoreAdditionalClasses : self.options.readMoreBtnAdditionalClasses;\n const btnText = (self.options.contentVisibilityState[thisIndex]) ? readLessText : readMoreText;\n const btnAriaExpanded = (self.options.contentVisibilityState[thisIndex]) ? 'true' : 'false';\n const btnMarkup = ' <' + btnTag + ' aria-expanded=\"' + btnAriaExpanded + '\" class=\"' + self.options.readMoreBtnSelectorClass + ' ' + btnAdditionalClasses + '\">' + btnText.replace(/<[^>]*>/g, \"\") + '';\n let btnExists;\n\n // check for button existence depending on btn position\n if (btnPosition == 'after' && currentElement.nextElementSibling) {\n btnExists = currentElement.nextElementSibling.matches(btnSelectorClass);\n } else if (btnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // insert element only if it doesn't exist\n if (!btnExists) {\n\n // add read-more button to dom\n switch (btnPosition) {\n case 'after':\n currentElement.insertAdjacentHTML('afterend', btnMarkup);\n break;\n case 'inside':\n currentElement.insertAdjacentHTML('beforeend', btnMarkup);\n break;\n default:\n console.log('no matching read-more button position defined');\n }\n\n // listen to read-more clicks - show/hide content\n if (!updated) {\n\n if (btnPosition == 'after') {\n currentElement.nextElementSibling.addEventListener('click',function(event) {\n if (event.target && event.target.classList.contains(self.options.readMoreBtnSelectorClass)) {\n updateContent(event, btnPosition);\n }\n });\n } else if (btnPosition == 'inside') {\n currentElement.addEventListener('click',function(event) {\n if (event.target && event.target.classList.contains(self.options.readMoreBtnSelectorClass)) {\n updateContent(event, btnPosition);\n }\n });\n }\n\n }\n\n }\n\n }\n\n\n /*\n display original/truncated content\n */\n function updateContent(event, btnPosition) {\n\n const currentElement = (btnPosition == 'after') ? event.target.previousElementSibling : event.target.parentNode;\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readMoreText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadmore : self.options.readMoreText;\n const readLessText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadless : self.options.readLessText;\n const truncateLength = (currentElement.dataset.cuttrLength) ? currentElement.dataset.cuttrLength : self.options.length;\n const truncateEnding = (currentElement.dataset.cuttrEnding) ? currentElement.dataset.cuttrEnding : self.options.ending;\n let truncatedContent;\n\n // show content if its currently truncated\n if (!self.options.contentVisibilityState[thisIndex]) {\n\n // replace content with original content from element at specific index\n currentElement.innerHTML = self.options.originalContent[thisIndex];\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = true;\n\n if (btnPosition == 'inside' && self.options.readMore)\n addReadMore(currentElement, true);\n\n // update button text and aria\n event.target.innerHTML = readLessText.replace(/<[^>]*>/g, \"\");\n //event.target.setAttribute('aria-expanded', 'true');\n\n // here go the callbacks\n self.options.afterExpand.call(currentElement);\n\n // truncate content if its shown completely currently\n } else {\n\n // truncate content\n truncatedContent = truncateIt(currentElement, currentContent.trim(), truncateLength, truncateEnding);\n currentElement.innerHTML = truncatedContent;\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n\n if (btnPosition == 'inside' && self.options.readMore)\n addReadMore(currentElement, true);\n\n // update button text and aria\n event.target.innerHTML = readMoreText.replace(/<[^>]*>/g, \"\");\n //event.target.setAttribute('aria-expanded', 'false');\n\n // here go the callbacks\n self.options.afterTruncate.call(currentElement);\n\n }\n\n }\n\n\n /**\n * Displays warnings\n */\n function displayWarnings(isAuthorized) {\n\n if (!isAuthorized) {\n showError('error', 'Cuttr.js has a GPLv3 license and it requires a `licenseKey` option. Read about it here:');\n showError('error', 'https://github.com/d-e-v-s-k/cuttr-js#options');\n }\n\n }\n\n\n /*\n public function\n expand / show original content\n */\n self.expandContent = function(selector, btnPosition) {\n\n let currentElements;\n\n // set specific element to expand or use current instance node\n if (selector) {\n currentElements = document.querySelectorAll(selector);\n } else {\n currentElements = self.options.elementsToTruncate;\n }\n\n for (let i = 0; i < currentElements.length; i++) {\n\n const currentElement = currentElements[i];\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readLessText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadless : self.options.readLessText;\n const thisBtnPosition = (btnPosition) ? btnPosition : self.options.readMoreBtnPosition;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n let btnExists;\n\n // show content if its currently truncated\n if (!self.options.contentVisibilityState[thisIndex]) {\n\n // replace content with original content from element at specific index\n currentElement.innerHTML = self.options.originalContent[thisIndex];\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = true;\n\n // read-more handling only if enabled\n if (self.options.readMore) {\n\n if (thisBtnPosition == 'inside')\n addReadMore(currentElement, true);\n\n // check for button existence depending on btn position\n if (thisBtnPosition == 'after') {\n btnExists = currentElement.nextElementSibling;\n } else if (thisBtnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // update button text\n if(btnExists)\n btnExists.innerHTML = readLessText.replace(/<[^>]*>/g, \"\");\n\n }\n\n // here go the callbacks\n self.options.afterExpand.call(currentElement);\n\n }\n\n }\n\n }\n\n\n /*\n public function\n truncate / hide original content\n */\n self.truncateContent = function(selector, btnPosition) {\n\n let currentElements;\n\n // set specific element to expand or use current instance node\n if (selector) {\n currentElements = document.querySelectorAll(selector);\n } else {\n currentElements = self.options.elementsToTruncate;\n }\n\n for (let i = 0; i < currentElements.length; i++) {\n\n const currentElement = currentElements[i];\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readMoreText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadmore : self.options.readMoreText;\n const thisBtnPosition = (btnPosition) ? btnPosition : self.options.readMoreBtnPosition;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n const truncateLength = (currentElement.dataset.cuttrLength) ? currentElement.dataset.cuttrLength : self.options.length;\n const truncateEnding = (currentElement.dataset.cuttrEnding) ? currentElement.dataset.cuttrEnding : self.options.ending;\n let truncatedContent;\n let btnExists;\n\n // hide content if its currently fully visible\n if (self.options.contentVisibilityState[thisIndex]) {\n\n // truncate content\n truncatedContent = truncateIt(currentElement, currentContent.trim(), truncateLength, truncateEnding);\n currentElement.innerHTML = truncatedContent;\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n\n // read-more handling only if enabled\n if (self.options.readMore) {\n\n if (thisBtnPosition == 'inside')\n addReadMore(currentElement, true);\n\n // check for button existence depending on btn position\n if (thisBtnPosition == 'after') {\n btnExists = currentElement.nextElementSibling;\n } else if (thisBtnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // update button text\n if(btnExists)\n btnExists.innerHTML = readMoreText.replace(/<[^>]*>/g, \"\");\n\n }\n\n // here go the callbacks\n self.options.afterTruncate.call(currentElement);\n\n }\n\n }\n\n }\n\n\n /*\n public function\n restore the element to a pre-init state\n */\n self.destroy = function(selector, btnPosition) {\n\n // expand original content\n self.expandContent(selector, btnPosition);\n\n let currentElements;\n\n // set specific element to expand or use current instance node\n if (selector) {\n currentElements = document.querySelectorAll(selector);\n } else {\n currentElements = self.options.elementsToTruncate;\n }\n\n for (let i = 0; i < currentElements.length; i++) {\n\n let currentElement = currentElements[i];\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const thisBtnPosition = (btnPosition) ? btnPosition : self.options.readMoreBtnPosition;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n let btnExists;\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = true;\n\n // remove read-more if enabled\n if (self.options.readMore) {\n\n if (thisBtnPosition == 'inside')\n addReadMore(currentElement, true);\n\n // check for button existence depending on btn position\n if (thisBtnPosition == 'after') {\n btnExists = currentElement.nextElementSibling;\n } else if (thisBtnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // remove button\n if(btnExists)\n btnExists.parentNode.removeChild(btnExists);\n //btnExists.remove();\n\n }\n\n // remove element classes\n currentElement.classList.remove(self.options.loadedClass);\n\n // remove truncate-element index from element\n currentElement.removeAttribute(self.options.dataIndex);\n\n // reset current truncation instance\n currentElement = null;\n\n }\n\n }\n\n\n //utils\n /*\n shows console message\n */\n function showError(type, text){\n window.console && window.console[type] && window.console[type]('Cuttr: ' + text);\n }\n\n init();\n return self;\n };\n return Cuttr;\n}));\n\n\n/**\n * jQuery adapter for Cuttr.js 1.4.0\n */\nif(window.jQuery && window.Cuttr){\n (function ($, Cuttr) {\n 'use strict';\n\n // No jQuery No Go\n if (!$ || !Cuttr) {\n //window.cuttr_utils.showError('error', 'jQuery is required to use the jQuery Cuttr adapter!');\n console.log('ERROR - jQuery is required to use the jQuery Cuttr adapter!');\n return;\n }\n\n $.fn.Cuttr = function (options) {\n return this.each((e, element) => {\n options = $.extend({}, options, {'$': $});\n if (!$.data(element, 'Cuttr')) {\n $.data(element, 'Cuttr', new Cuttr(element, options));\n }\n });\n };\n })(window.jQuery, window.Cuttr);\n}\n"]} \ No newline at end of file +{"version":3,"sources":["cuttr.js"],"names":["root","define","amd","factory","module","exports","Cuttr","this","Object","self","el","create","prototype","originalContent","contentTruncationState","document","querySelectorAll","truncate","length","loadedClass","title","readMore","readLessText","readMoreText","readMoreBtnPosition","readMoreBtnAdditionalClasses","readMoreBtnSelectorClass","afterExpand","afterTruncate","options","keys","forEach","key","truncateIt","thisElement","str","ending","thisIndex","dataset","cuttrIndex","contentVisibilityState","cuttrMethod","splice","substring","words","split","sentences","join","match","currentElement","btnAriaExpanded","btnPosition","addReadMore","updated","updateContent","cuttrReadmore","nextElementSibling","addEventListener","cuttrReadless","event","target","contains","cuttrReadmorePosition","cuttrReadmoreTag","readMoreBtnTag","btnSelectorClass","btnAdditionalClasses","cuttrReadmoreAdditionalClasses","btnText","classList","replace","btnTag","btnExists","matches","querySelector","currentContent","btnMarkup","cuttrLength","truncatedContent","console","log","trim","truncateLength","truncateEnding","isAuthorized","previousElementSibling","parentNode","innerHTML","CUTTR_LICENSE","cuttrEnding","thisBtnPosition","call","currentElements","selector","showError","type","text","window","elementsToTruncate","i","truncateContent","dataIndex","$","destroy","expandContent","removeChild","remove","removeAttribute","RegExp","test","domain","indexOf","displayWarnings","contentToTitle","cuttrTitle","setAttribute","textContent","init","jQuery","fn","each","e","element","extend","data"],"mappings":"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","file":"cuttr.min.js","sourcesContent":["/*!\n * Cuttr 1.4.1\n * https://github.com/d-e-v-s-k/cuttr-js\n *\n * @license GPLv3 for open source use only\n * or Cuttr Commercial License for commercial use\n * https://cuttr.kulahs.de/pricing/\n *\n * Copyright (C) 2022 https://cuttr.kulahs.de/ - A project by DEVSK\n **/\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define([], factory);\n } else if (typeof module === 'object' && module.exports) {\n // Node. Does not work with strict CommonJS, but\n // only CommonJS-like environments that support module.exports,\n // like Node.\n module.exports = factory();\n } else {\n // Browser globals (root is window)\n root.Cuttr = factory();\n }\n}(this, function () {\n\n // private global vars\n let CUTTR_LICENSE = true;\n\n // init Cuttr\n const Cuttr = function (el, options){\n 'use strict';\n\n const self = Object.create(Cuttr.prototype);\n\n /**\n * Default settings\n */\n self.options = {\n // global data\n elementsToTruncate: typeof el === 'string' ? document.querySelectorAll(el) : el,\n originalContent: [],\n contentVisibilityState: [],\n contentTruncationState: [],\n\n // set default options\n truncate: 'characters', // truncate method [characters|words|sentences]\n length: 100, // truncation limit\n ending: '...', // truncation ending string\n loadedClass: 'cuttr--loaded', // class to set when truncation finished\n title: false, // add original content to elements title tag\n readMore: false, // read more button enabled/disabled\n readMoreText: 'read more',\n readLessText: 'read less',\n readMoreBtnPosition: 'after', // [after|inside]\n readMoreBtnTag: 'button', // read-more button tag [button|a|...]\n readMoreBtnSelectorClass: 'cuttr__readmore', // read-more button selector\n readMoreBtnAdditionalClasses: '',\n\n // callback functions\n afterTruncate: function(){},\n afterExpand: function(){},\n\n // private options\n dataIndex: 'data-cuttr-index', // cuttr index data attribute\n };\n\n /**\n * User defined options\n */\n if (options) {\n Object.keys(options).forEach(function (key){\n self.options[key] = options[key];\n });\n }\n\n\n const init = function () {\n prepare.call(this);\n };\n\n\n /*\n prepare cuttable elements\n */\n function prepare() {\n\n const isAuthorized = self.options && new RegExp('([\\\\d\\\\w]{8}-){3}[\\\\d\\\\w]{8}|^(?=.*?[A-Y])(?=.*?[a-y])(?=.*?[0-8])(?=.*?[#?!@$%^&*-]).{8,}$').test(self.options['li'+'cen'+'seK' + 'e' + 'y']) || document.domain.indexOf('cuttr'+'.' +'kul' + 'ahs' + '.' + 'de') > -1;\n\n // return if no target element defined\n if (!self.options.elementsToTruncate) {\n return;\n } else {\n displayWarnings(isAuthorized);\n }\n\n // set element type depending on source\n if ( !('length' in self.options.elementsToTruncate) )\n self.options.elementsToTruncate = [self.options.elementsToTruncate];\n\n // loop through target elements to truncate\n for (let i = 0; i < self.options.elementsToTruncate.length; i++) {\n\n const currentElement = self.options.elementsToTruncate[i];\n const currentContent = currentElement.innerHTML;\n const truncateLength = (currentElement.dataset.cuttrLength) ? currentElement.dataset.cuttrLength : self.options.length;\n const truncateEnding = (currentElement.dataset.cuttrEnding) ? currentElement.dataset.cuttrEnding : self.options.ending;\n const contentToTitle = (currentElement.dataset.cuttrTitle) ? currentElement.dataset.cuttrTitle : self.options.title;\n let truncatedContent;\n\n // add truncate-element index to element\n currentElement.setAttribute(self.options.dataIndex, i);\n\n // temporary save elements original content\n self.options.originalContent.push(currentContent);\n\n // truncate content\n truncatedContent = truncateIt(currentElement, currentContent.trim(), truncateLength, truncateEnding);\n\n // set title attr with original text content\n if (contentToTitle)\n currentElement.title = currentElement.textContent.trim();\n\n // set new content\n currentElement.innerHTML = truncatedContent;\n\n // add read-more button if current content is truncated\n if (self.options.contentTruncationState[i]) {\n\n if (self.options.readMore)\n addReadMore(currentElement);\n\n currentElement.classList += ' ' + self.options.loadedClass;\n\n }\n\n // here go the callbacks\n self.options.afterTruncate.call(currentElement);\n\n }\n\n }\n\n\n /*\n truncate text to specific length\n */\n function truncateIt(thisElement, str, length, ending) {\n\n const thisIndex = thisElement.dataset.cuttrIndex;\n const truncateMethod = (thisElement.dataset.cuttrMethod) ? thisElement.dataset.cuttrMethod : self.options.truncate;\n\n // set defaults\n if (length == null) {\n length = 100;\n }\n\n // set defaults\n if (ending == null) {\n ending = '...';\n }\n\n // truncate content based on method\n switch (truncateMethod) {\n\n // truncate characters only\n case 'characters':\n\n // check if content (string) is longer than truncation limit\n if (str.length > length) {\n\n // set current content truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n\n // return new string\n return str.substring(0, length - ending.length) + ending + ' ';\n\n } else {\n return str;\n }\n\n break;\n\n // truncate words\n case 'words':\n\n const words = str.split(/ (?=[^>]*(?:<|$))/);\n\n // check if content (string) is longer than truncation limit\n if (words.length > length) {\n\n // set current content truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n // return new string\n // split spaces followed by sequence of characters are NOT greater-than signs, less-than sign\n return words.splice(0,length).join(' ') + ' ' + ending + ' ';\n\n } else {\n return str;\n }\n\n break;\n\n // truncate full sentences\n case 'sentences':\n\n const sentences = str.match(/[^\\.!\\?]+[\\.!\\?]+/g);\n\n // check if content (string) is longer than truncation limit\n if (sentences.length > length) {\n\n // set current contetn truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n // return new string\n return sentences.splice(0,length).join(' ') + ' ' + ending + ' ';\n\n } else {\n return str;\n }\n\n break;\n\n // truncate characters by default\n default:\n\n // check if content (string) is longer than truncation limit\n if (str.length > length) {\n\n // set current contetn truncation true and return truncated string\n self.options.contentTruncationState[thisIndex] = true;\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n // return new string\n return str.substring(0, length - ending.length) + ending;\n\n } else {\n return str;\n }\n\n }\n\n }\n\n\n /*\n append read more button\n */\n function addReadMore(thisElement, updated) {\n\n const currentElement = thisElement;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readMoreText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadmore : self.options.readMoreText;\n const readLessText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadless : self.options.readLessText;\n const btnPosition = (currentElement.dataset.cuttrReadmorePosition) ? currentElement.dataset.cuttrReadmorePosition : self.options.readMoreBtnPosition;\n const btnTag = (currentElement.dataset.cuttrReadmoreTag) ? currentElement.dataset.cuttrReadmoreTag : self.options.readMoreBtnTag;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n const btnAdditionalClasses = (currentElement.dataset.cuttrReadmoreAdditionalClasses) ? currentElement.dataset.cuttrReadmoreAdditionalClasses : self.options.readMoreBtnAdditionalClasses;\n const btnText = (self.options.contentVisibilityState[thisIndex]) ? readLessText : readMoreText;\n const btnAriaExpanded = (self.options.contentVisibilityState[thisIndex]) ? 'true' : 'false';\n const btnMarkup = ' <' + btnTag + ' aria-expanded=\"' + btnAriaExpanded + '\" class=\"' + self.options.readMoreBtnSelectorClass + ' ' + btnAdditionalClasses + '\">' + btnText.replace(/<[^>]*>/g, \"\") + '';\n let btnExists;\n\n // check for button existence depending on btn position\n if (btnPosition == 'after' && currentElement.nextElementSibling) {\n btnExists = currentElement.nextElementSibling.matches(btnSelectorClass);\n } else if (btnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // insert element only if it doesn't exist\n if (!btnExists) {\n\n // add read-more button to dom\n switch (btnPosition) {\n case 'after':\n currentElement.insertAdjacentHTML('afterend', btnMarkup);\n break;\n case 'inside':\n currentElement.insertAdjacentHTML('beforeend', btnMarkup);\n break;\n default:\n console.log('no matching read-more button position defined');\n }\n\n // listen to read-more clicks - show/hide content\n if (!updated) {\n\n if (btnPosition == 'after') {\n currentElement.nextElementSibling.addEventListener('click',function(event) {\n if (event.target && event.target.classList.contains(self.options.readMoreBtnSelectorClass)) {\n updateContent(event, btnPosition);\n }\n });\n } else if (btnPosition == 'inside') {\n currentElement.addEventListener('click',function(event) {\n if (event.target && event.target.classList.contains(self.options.readMoreBtnSelectorClass)) {\n updateContent(event, btnPosition);\n }\n });\n }\n\n }\n\n }\n\n }\n\n\n /*\n display original/truncated content\n */\n function updateContent(event, btnPosition) {\n\n const currentElement = (btnPosition == 'after') ? event.target.previousElementSibling : event.target.parentNode;\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readMoreText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadmore : self.options.readMoreText;\n const readLessText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadless : self.options.readLessText;\n const truncateLength = (currentElement.dataset.cuttrLength) ? currentElement.dataset.cuttrLength : self.options.length;\n const truncateEnding = (currentElement.dataset.cuttrEnding) ? currentElement.dataset.cuttrEnding : self.options.ending;\n let truncatedContent;\n\n // show content if its currently truncated\n if (!self.options.contentVisibilityState[thisIndex]) {\n\n // replace content with original content from element at specific index\n currentElement.innerHTML = self.options.originalContent[thisIndex];\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = true;\n\n if (btnPosition == 'inside' && self.options.readMore)\n addReadMore(currentElement, true);\n\n // update button text and aria\n event.target.innerHTML = readLessText.replace(/<[^>]*>/g, \"\");\n //event.target.setAttribute('aria-expanded', 'true');\n\n // here go the callbacks\n self.options.afterExpand.call(currentElement);\n\n // truncate content if its shown completely currently\n } else {\n\n // truncate content\n truncatedContent = truncateIt(currentElement, currentContent.trim(), truncateLength, truncateEnding);\n currentElement.innerHTML = truncatedContent;\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n\n if (btnPosition == 'inside' && self.options.readMore)\n addReadMore(currentElement, true);\n\n // update button text and aria\n event.target.innerHTML = readMoreText.replace(/<[^>]*>/g, \"\");\n //event.target.setAttribute('aria-expanded', 'false');\n\n // here go the callbacks\n self.options.afterTruncate.call(currentElement);\n\n }\n\n }\n\n\n /**\n * Displays warnings\n */\n function displayWarnings(isAuthorized) {\n\n if (!isAuthorized && CUTTR_LICENSE) {\n // declare global const to show error only once\n CUTTR_LICENSE = false;\n // show error message\n showError('error', 'Cuttr.js has a GPLv3 license and it requires a `licenseKey` option. Read about it here:');\n showError('error', 'https://github.com/d-e-v-s-k/cuttr-js#options');\n }\n\n }\n\n\n /*\n public function\n expand / show original content\n */\n self.expandContent = function(selector, btnPosition) {\n\n let currentElements;\n\n // set specific element to expand or use current instance node\n if (selector) {\n currentElements = document.querySelectorAll(selector);\n } else {\n currentElements = self.options.elementsToTruncate;\n }\n\n for (let i = 0; i < currentElements.length; i++) {\n\n const currentElement = currentElements[i];\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readLessText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadless : self.options.readLessText;\n const thisBtnPosition = (btnPosition) ? btnPosition : self.options.readMoreBtnPosition;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n let btnExists;\n\n // show content if its currently truncated\n if (!self.options.contentVisibilityState[thisIndex]) {\n\n // replace content with original content from element at specific index\n currentElement.innerHTML = self.options.originalContent[thisIndex];\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = true;\n\n // read-more handling only if enabled\n if (self.options.readMore) {\n\n if (thisBtnPosition == 'inside')\n addReadMore(currentElement, true);\n\n // check for button existence depending on btn position\n if (thisBtnPosition == 'after') {\n btnExists = currentElement.nextElementSibling;\n } else if (thisBtnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // update button text\n if(btnExists)\n btnExists.innerHTML = readLessText.replace(/<[^>]*>/g, \"\");\n\n }\n\n // here go the callbacks\n self.options.afterExpand.call(currentElement);\n\n }\n\n }\n\n }\n\n\n /*\n public function\n truncate / hide original content\n */\n self.truncateContent = function(selector, btnPosition) {\n\n let currentElements;\n\n // set specific element to expand or use current instance node\n if (selector) {\n currentElements = document.querySelectorAll(selector);\n } else {\n currentElements = self.options.elementsToTruncate;\n }\n\n for (let i = 0; i < currentElements.length; i++) {\n\n const currentElement = currentElements[i];\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const readMoreText = (currentElement.dataset.cuttrReadmore) ? currentElement.dataset.cuttrReadmore : self.options.readMoreText;\n const thisBtnPosition = (btnPosition) ? btnPosition : self.options.readMoreBtnPosition;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n const truncateLength = (currentElement.dataset.cuttrLength) ? currentElement.dataset.cuttrLength : self.options.length;\n const truncateEnding = (currentElement.dataset.cuttrEnding) ? currentElement.dataset.cuttrEnding : self.options.ending;\n let truncatedContent;\n let btnExists;\n\n // hide content if its currently fully visible\n if (self.options.contentVisibilityState[thisIndex]) {\n\n // truncate content\n truncatedContent = truncateIt(currentElement, currentContent.trim(), truncateLength, truncateEnding);\n currentElement.innerHTML = truncatedContent;\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = false;\n\n // read-more handling only if enabled\n if (self.options.readMore) {\n\n if (thisBtnPosition == 'inside')\n addReadMore(currentElement, true);\n\n // check for button existence depending on btn position\n if (thisBtnPosition == 'after') {\n btnExists = currentElement.nextElementSibling;\n } else if (thisBtnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // update button text\n if(btnExists)\n btnExists.innerHTML = readMoreText.replace(/<[^>]*>/g, \"\");\n\n }\n\n // here go the callbacks\n self.options.afterTruncate.call(currentElement);\n\n }\n\n }\n\n }\n\n\n /*\n public function\n restore the element to a pre-init state\n */\n self.destroy = function(selector, btnPosition) {\n\n // expand original content\n self.expandContent(selector, btnPosition);\n\n let currentElements;\n\n // set specific element to expand or use current instance node\n if (selector) {\n currentElements = document.querySelectorAll(selector);\n } else {\n currentElements = self.options.elementsToTruncate;\n }\n\n for (let i = 0; i < currentElements.length; i++) {\n\n let currentElement = currentElements[i];\n const currentContent = currentElement.innerHTML;\n const thisIndex = currentElement.dataset.cuttrIndex;\n const thisBtnPosition = (btnPosition) ? btnPosition : self.options.readMoreBtnPosition;\n const btnSelectorClass = '.' + self.options.readMoreBtnSelectorClass;\n let btnExists;\n\n // set visibility state\n self.options.contentVisibilityState[thisIndex] = true;\n\n // remove read-more if enabled\n if (self.options.readMore) {\n\n if (thisBtnPosition == 'inside')\n addReadMore(currentElement, true);\n\n // check for button existence depending on btn position\n if (thisBtnPosition == 'after') {\n btnExists = currentElement.nextElementSibling;\n } else if (thisBtnPosition == 'inside') {\n btnExists = currentElement.querySelector(btnSelectorClass);\n }\n\n // remove button\n if(btnExists)\n btnExists.parentNode.removeChild(btnExists);\n //btnExists.remove();\n\n }\n\n // remove element classes\n currentElement.classList.remove(self.options.loadedClass);\n\n // remove truncate-element index from element\n currentElement.removeAttribute(self.options.dataIndex);\n\n // reset current truncation instance\n currentElement = null;\n\n }\n\n }\n\n\n //utils\n /*\n shows console message\n */\n function showError(type, text){\n window.console && window.console[type] && window.console[type]('Cuttr: ' + text);\n }\n\n init();\n return self;\n };\n return Cuttr;\n}));\n\n\n/**\n * jQuery adapter for Cuttr.js 1.4.0\n */\nif(window.jQuery && window.Cuttr){\n (function ($, Cuttr) {\n 'use strict';\n\n // No jQuery No Go\n if (!$ || !Cuttr) {\n //window.cuttr_utils.showError('error', 'jQuery is required to use the jQuery Cuttr adapter!');\n console.log('ERROR - jQuery is required to use the jQuery Cuttr adapter!');\n return;\n }\n\n $.fn.Cuttr = function (options) {\n return this.each((e, element) => {\n options = $.extend({}, options, {'$': $});\n if (!$.data(element, 'Cuttr')) {\n $.data(element, 'Cuttr', new Cuttr(element, options));\n }\n });\n };\n })(window.jQuery, window.Cuttr);\n}\n"]} \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 75d7f3c..8988b3e 100644 --- a/examples/index.html +++ b/examples/index.html @@ -54,8 +54,6 @@

Truncate without breaking the HTML

length: 13 }); - truncateChar.expandContent(); - new Cuttr('.truncate-words', { //options here truncate: 'words', diff --git a/package.json b/package.json index ed2da5b..9620ce0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cuttr", - "version": "1.4.0", + "version": "1.4.1", "description": "Cuttr is a javascript / jQuery plugin that truncates multi-line string content with multiple truncation methods and custom ellipsis.", "main": "dist/cuttr.js", "unpkg": "dist/cuttr.min.js", diff --git a/src/cuttr.js b/src/cuttr.js index dc6e71b..62525b1 100644 --- a/src/cuttr.js +++ b/src/cuttr.js @@ -1,5 +1,5 @@ /*! - * Cuttr 1.4.0 + * Cuttr 1.4.1 * https://github.com/d-e-v-s-k/cuttr-js * * @license GPLv3 for open source use only @@ -23,6 +23,11 @@ root.Cuttr = factory(); } }(this, function () { + + // private global vars + let CUTTR_LICENSE = true; + + // init Cuttr const Cuttr = function (el, options){ 'use strict'; @@ -370,7 +375,10 @@ */ function displayWarnings(isAuthorized) { - if (!isAuthorized) { + if (!isAuthorized && CUTTR_LICENSE) { + // declare global const to show error only once + CUTTR_LICENSE = false; + // show error message showError('error', 'Cuttr.js has a GPLv3 license and it requires a `licenseKey` option. Read about it here:'); showError('error', 'https://github.com/d-e-v-s-k/cuttr-js#options'); }