diff --git a/app/assets/javascripts/vendor.js b/app/assets/javascripts/vendor.js index faa0d144b5e..4b500d18bc8 100644 --- a/app/assets/javascripts/vendor.js +++ b/app/assets/javascripts/vendor.js @@ -33,7 +33,7 @@ //= require show-html.js //= require break_string //= require buffered-proxy -//= require jquery.autoellipsis-1.0.10.min.js +//= require jquery.autoellipsis-1.0.10 //= require virtual-dom //= require virtual-dom-amd //= require highlight.js diff --git a/vendor/assets/javascripts/jquery.autoellipsis-1.0.10.js b/vendor/assets/javascripts/jquery.autoellipsis-1.0.10.js new file mode 100644 index 00000000000..7bd28ef0ec1 --- /dev/null +++ b/vendor/assets/javascripts/jquery.autoellipsis-1.0.10.js @@ -0,0 +1,447 @@ +/*! + + Copyright (c) 2011 Peter van der Spek + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + */ + + +(function($) { + + /** + * Hash containing mapping of selectors to settings hashes for target selectors that should be live updated. + * + * @type {Object.} + * @private + */ + var liveUpdatingTargetSelectors = {}; + + /** + * Interval ID for live updater. Contains interval ID when the live updater interval is active, or is undefined + * otherwise. + * + * @type {number} + * @private + */ + var liveUpdaterIntervalId; + + /** + * Boolean indicating whether the live updater is running. + * + * @type {boolean} + * @private + */ + var liveUpdaterRunning = false; + + /** + * Set of default settings. + * + * @type {Object.} + * @private + */ + var defaultSettings = { + ellipsis: '...', + setTitle: 'never', + live: false + }; + + /** + * Perform ellipsis on selected elements. + * + * @param {string} selector the inner selector of elements that ellipsis may work on. Inner elements not referred to by this + * selector are left untouched. + * @param {Object.=} options optional options to override default settings. + * @return {jQuery} the current jQuery object for chaining purposes. + * @this {jQuery} the current jQuery object. + */ + $.fn.ellipsis = function(selector, options) { + var subjectElements, settings; + + subjectElements = $(this); + + // Check for options argument only. + if (typeof selector !== 'string') { + options = selector; + selector = undefined; + } + + // Create the settings from the given options and the default settings. + settings = $.extend({}, defaultSettings, options); + + // If selector is not set, work on immediate children (default behaviour). + settings.selector = selector; + + // Do ellipsis on each subject element. + subjectElements.each(function() { + var elem = $(this); + + // Do ellipsis on subject element. + ellipsisOnElement(elem, settings); + }); + + // If live option is enabled, add subject elements to live updater. Otherwise remove from live updater. + if (settings.live) { + addToLiveUpdater(subjectElements.selector, settings); + + } else { + removeFromLiveUpdater(subjectElements.selector); + } + + // Return jQuery object for chaining. + return this; + }; + + + /** + * Perform ellipsis on the given container. + * + * @param {jQuery} containerElement jQuery object containing one DOM element to perform ellipsis on. + * @param {Object.} settings the settings for this ellipsis operation. + * @private + */ + function ellipsisOnElement(containerElement, settings) { + var containerData = containerElement.data('jqae'); + if (!containerData) containerData = {}; + + // Check if wrapper div was already created and bound to the container element. + var wrapperElement = containerData.wrapperElement; + + // If not, create wrapper element. + if (!wrapperElement) { + wrapperElement = containerElement.wrapInner('
').find('>div'); + + // Wrapper div should not add extra size. + wrapperElement.css({ + margin: 0, + padding: 0, + border: 0 + }); + } + + // Check if the original wrapper element content was already bound to the wrapper element. + var wrapperElementData = wrapperElement.data('jqae'); + if (!wrapperElementData) wrapperElementData = {}; + + var wrapperOriginalContent = wrapperElementData.originalContent; + + // If so, clone the original content, re-bind the original wrapper content to the clone, and replace the + // wrapper with the clone. + if (wrapperOriginalContent) { + wrapperElement = wrapperElementData.originalContent.clone(true) + .data('jqae', {originalContent: wrapperOriginalContent}).replaceAll(wrapperElement); + + } else { + // Otherwise, clone the current wrapper element and bind it as original content to the wrapper element. + + wrapperElement.data('jqae', {originalContent: wrapperElement.clone(true)}); + } + + // Bind the wrapper element and current container width and height to the container element. Current container + // width and height are stored to detect changes to the container size. + containerElement.data('jqae', { + wrapperElement: wrapperElement, + containerWidth: containerElement.width(), + containerHeight: containerElement.height() + }); + + // Calculate with current container element height. + var containerElementHeight = containerElement.height(); + + // Calculate wrapper offset. + var wrapperOffset = (parseInt(containerElement.css('padding-top'), 10) || 0) + (parseInt(containerElement.css('border-top-width'), 10) || 0) - (wrapperElement.offset().top - containerElement.offset().top); + + // Normally the ellipsis characters are applied to the last non-empty text-node in the selected element. If the + // selected element becomes empty during ellipsis iteration, the ellipsis characters cannot be applied to that + // selected element, and must be deferred to the previous selected element. This parameter keeps track of that. + var deferAppendEllipsis = false; + + // Loop through all selected elements in reverse order. + var selectedElements = wrapperElement; + if (settings.selector) selectedElements = $(wrapperElement.find(settings.selector).get().reverse()); + + selectedElements.each(function() { + var selectedElement = $(this), + originalText = selectedElement.text(), + ellipsisApplied = false; + + // Check if we can safely remove the selected element. This saves a lot of unnecessary iterations. + if (wrapperElement.innerHeight() - selectedElement.innerHeight() > containerElementHeight + wrapperOffset) { + selectedElement.remove(); + + } else { + // Reverse recursively remove empty elements, until the element that contains a non-empty text-node. + removeLastEmptyElements(selectedElement); + + // If the selected element has not become empty, start ellipsis iterations on the selected element. + if (selectedElement.contents().length) { + + // If a deffered ellipsis is still pending, apply it now to the last text-node. + if (deferAppendEllipsis) { + getLastTextNode(selectedElement).get(0).nodeValue += settings.ellipsis; + deferAppendEllipsis = false; + } + + // Iterate until wrapper element height is less than or equal to the original container element + // height plus possible wrapperOffset. + while (wrapperElement.innerHeight() > containerElementHeight + wrapperOffset) { + // Apply ellipsis on last text node, by removing one word. + ellipsisApplied = ellipsisOnLastTextNode(selectedElement); + + // If ellipsis was succesfully applied, remove any remaining empty last elements and append the + // ellipsis characters. + if (ellipsisApplied) { + removeLastEmptyElements(selectedElement); + + // If the selected element is not empty, append the ellipsis characters. + if (selectedElement.contents().length) { + getLastTextNode(selectedElement).get(0).nodeValue += settings.ellipsis; + + } else { + // If the selected element has become empty, defer the appending of the ellipsis characters + // to the previous selected element. + deferAppendEllipsis = true; + selectedElement.remove(); + break; + } + + } else { + // If ellipsis could not be applied, defer the appending of the ellipsis characters to the + // previous selected element. + deferAppendEllipsis = true; + selectedElement.remove(); + break; + } + } + + // If the "setTitle" property is set to "onEllipsis" and the ellipsis has been applied, or if the + // property is set to "always", the add the "title" attribute with the original text. Else remove the + // "title" attribute. When the "setTitle" property is set to "never" we do not touch the "title" + // attribute. + if (((settings.setTitle == 'onEllipsis') && ellipsisApplied) || (settings.setTitle == 'always')) { + selectedElement.attr('title', originalText); + + } else if (settings.setTitle != 'never') { + selectedElement.removeAttr('title'); + } + } + } + }); + } + + /** + * Performs ellipsis on the last text node of the given element. Ellipsis is done by removing a full word. + * + * @param {jQuery} element jQuery object containing a single DOM element. + * @return {boolean} true when ellipsis has been done, false otherwise. + * @private + */ + function ellipsisOnLastTextNode(element) { + var lastTextNode = getLastTextNode(element); + + // If the last text node is found, do ellipsis on that node. + if (lastTextNode.length) { + var text = lastTextNode.get(0).nodeValue; + + // Find last space character, and remove text from there. If no space is found the full remaining text is + // removed. + var pos = text.lastIndexOf(' '); + if (pos > -1) { + text = $.trim(text.substring(0, pos)); + lastTextNode.get(0).nodeValue = text; + + } else { + lastTextNode.get(0).nodeValue = ''; + } + + return true; + } + + return false; + } + + /** + * Get last text node of the given element. + * + * @param {jQuery} element jQuery object containing a single element. + * @return {jQuery} jQuery object containing a single text node. + * @private + */ + function getLastTextNode(element) { + if (element.contents().length) { + + // Get last child node. + var contents = element.contents(); + var lastNode = contents.eq(contents.length - 1); + + // If last node is a text node, return it. + if (lastNode.filter(textNodeFilter).length) { + return lastNode; + + } else { + // Else it is an element node, and we recurse into it. + + return getLastTextNode(lastNode); + } + + } else { + // If there is no last child node, we append an empty text node and return that. Normally this should not + // happen, as we test for emptiness before calling getLastTextNode. + + element.append(''); + var contents = element.contents(); + return contents.eq(contents.length - 1); + } + } + + /** + * Remove last empty elements. This is done recursively until the last element contains a non-empty text node. + * + * @param {jQuery} element jQuery object containing a single element. + * @return {boolean} true when elements have been removed, false otherwise. + * @private + */ + function removeLastEmptyElements(element) { + if (element.contents().length) { + + // Get last child node. + var contents = element.contents(); + var lastNode = contents.eq(contents.length - 1); + + // If last child node is a text node, check for emptiness. + if (lastNode.filter(textNodeFilter).length) { + var text = lastNode.get(0).nodeValue; + text = $.trim(text); + + if (text == '') { + // If empty, remove the text node. + lastNode.remove(); + + return true; + + } else { + return false; + } + + } else { + // If the last child node is an element node, remove the last empty child nodes on that node. + while (removeLastEmptyElements(lastNode)) { + } + + // If the last child node contains no more child nodes, remove the last child node. + if (lastNode.contents().length) { + return false; + + } else { + lastNode.remove(); + + return true; + } + } + } + + return false; + } + + /** + * Filter for testing on text nodes. + * + * @return {boolean} true when this node is a text node, false otherwise. + * @this {Node} + * @private + */ + function textNodeFilter() { + return this.nodeType === 3; + } + + /** + * Add target selector to hash of target selectors. If this is the first target selector added, start the live + * updater. + * + * @param {string} targetSelector the target selector to run the live updater for. + * @param {Object.} settings the settings to apply on this target selector. + * @private + */ + function addToLiveUpdater(targetSelector, settings) { + // Store target selector with its settings. + liveUpdatingTargetSelectors[targetSelector] = settings; + + // If the live updater has not yet been started, start it now. + if (!liveUpdaterIntervalId) { + liveUpdaterIntervalId = window.setInterval(function() { + doLiveUpdater(); + }, 200); + } + } + + /** + * Remove the target selector from the hash of target selectors. It this is the last remaining target selector + * being removed, stop the live updater. + * + * @param {string} targetSelector the target selector to stop running the live updater for. + * @private + */ + function removeFromLiveUpdater(targetSelector) { + // If the hash contains the target selector, remove it. + if (liveUpdatingTargetSelectors[targetSelector]) { + delete liveUpdatingTargetSelectors[targetSelector]; + + // If no more target selectors are in the hash, stop the live updater. + if (!liveUpdatingTargetSelectors.length) { + if (liveUpdaterIntervalId) { + window.clearInterval(liveUpdaterIntervalId); + liveUpdaterIntervalId = undefined; + } + } + } + }; + + /** + * Run the live updater. The live updater is periodically run to check if its monitored target selectors require + * re-applying of the ellipsis. + * + * @private + */ + function doLiveUpdater() { + // If the live updater is already running, skip this time. We only want one instance running at a time. + if (!liveUpdaterRunning) { + liveUpdaterRunning = true; + + // Loop through target selectors. + for (var targetSelector in liveUpdatingTargetSelectors) { + $(targetSelector).each(function() { + var containerElement, containerData; + + containerElement = $(this); + containerData = containerElement.data('jqae'); + + // If container element dimensions have changed, or the container element is new, run ellipsis on + // that container element. + if ((containerData.containerWidth != containerElement.width()) || + (containerData.containerHeight != containerElement.height())) { + ellipsisOnElement(containerElement, liveUpdatingTargetSelectors[targetSelector]); + } + }); + } + + liveUpdaterRunning = false; + } + }; + +})(jQuery); \ No newline at end of file diff --git a/vendor/assets/javascripts/jquery.autoellipsis-1.0.10.min.js b/vendor/assets/javascripts/jquery.autoellipsis-1.0.10.min.js deleted file mode 100644 index ffc82eb4360..00000000000 --- a/vendor/assets/javascripts/jquery.autoellipsis-1.0.10.min.js +++ /dev/null @@ -1,23 +0,0 @@ -/*! - - Copyright (c) 2011 Peter van der Spek - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - - */(function(a){function m(){if(!d){d=!0;for(var c in b)a(c).each(function(){var d,e;d=a(this),e=d.data("jqae"),(e.containerWidth!=d.width()||e.containerHeight!=d.height())&&f(d,b[c])});d=!1}}function l(a){b[a]&&(delete b[a],b.length||c&&(window.clearInterval(c),c=undefined))}function k(a,d){b[a]=d,c||(c=window.setInterval(function(){m()},200))}function j(){return this.nodeType===3}function i(b){if(b.contents().length){var c=b.contents(),d=c.eq(c.length-1);if(d.filter(j).length){var e=d.get(0).nodeValue;e=a.trim(e);if(e==""){d.remove();return!0}return!1}while(i(d));if(d.contents().length)return!1;d.remove();return!0}return!1}function h(a){if(a.contents().length){var b=a.contents(),c=b.eq(b.length-1);return c.filter(j).length?c:h(c)}a.append("");var b=a.contents();return b.eq(b.length-1)}function g(b){var c=h(b);if(c.length){var d=c.get(0).nodeValue,e=d.lastIndexOf(" ");e>-1?(d=a.trim(d.substring(0,e)),c.get(0).nodeValue=d):c.get(0).nodeValue="";return!0}return!1}function f(b,c){var d=b.data("jqae");d||(d={});var e=d.wrapperElement;e||(e=b.wrapInner("
").find(">div"),e.css({margin:0,padding:0,border:0}));var f=e.data("jqae");f||(f={});var j=f.originalContent;j?e=f.originalContent.clone(!0).data("jqae",{originalContent:j}).replaceAll(e):e.data("jqae",{originalContent:e.clone(!0)}),b.data("jqae",{wrapperElement:e,containerWidth:b.width(),containerHeight:b.height()});var k=b.height(),l=(parseInt(b.css("padding-top"),10)||0)+(parseInt(b.css("border-top-width"),10)||0)-(e.offset().top-b.offset().top),m=!1,n=e;c.selector&&(n=a(e.find(c.selector).get().reverse())),n.each(function(){var b=a(this),d=b.text(),f=!1;if(e.innerHeight()-b.innerHeight()>k+l)b.remove();else{i(b);if(b.contents().length){m&&(h(b).get(0).nodeValue+=c.ellipsis,m=!1);while(e.innerHeight()>k+l){f=g(b);if(!f){m=!0,b.remove();break}i(b);if(b.contents().length)h(b).get(0).nodeValue+=c.ellipsis;else{m=!0,b.remove();break}}c.setTitle=="onEllipsis"&&f||c.setTitle=="always"?b.attr("title",d):c.setTitle!="never"&&b.removeAttr("title")}}})}var b={},c,d=!1,e={ellipsis:"...",setTitle:"never",live:!1};a.fn.ellipsis=function(b,c){var d,g;d=a(this),typeof b!="string"&&(c=b,b=undefined),g=a.extend({},e,c),g.selector=b,d.each(function(){var b=a(this);f(b,g)}),g.live?k(d.selector,g):l(d.selector);return this}})(jQuery) \ No newline at end of file