mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
DEV: Remove jQuery autoellipsis dependency (#15336)
This commit is contained in:
@@ -1,447 +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($) {
|
||||
|
||||
/**
|
||||
* Hash containing mapping of selectors to settings hashes for target selectors that should be live updated.
|
||||
*
|
||||
* @type {Object.<string, 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.<string, string>}
|
||||
* @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.<string, string>=} 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.<string, string>} 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('<div/>').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.<string, string>} 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);
|
||||
Reference in New Issue
Block a user