PageSourceSearch

https://wortwuchs.net/wp-content/themes/gridlove/assets/js/autoellipsis.js?ver=2.1.1

js wortwuchs.net collected 2026-09-24 19:22:34 UTC 16,496 bytes, 422 lines download raw bytes

1(function($) {
2
3    /**
4     * Hash containing mapping of selectors to settings hashes for target selectors that should be live updated.
5     *
6     * @type {Object.<string, Object>}
7     * @private
8     */
9    var liveUpdatingTargetSelectors = {};
10
11    /**
12     * Interval ID for live updater. Contains interval ID when the live updater interval is active, or is undefined
13     * otherwise.
14     *
15     * @type {number}
16     * @private
17     */
18    var liveUpdaterIntervalId;
19
20    /**
21     * Boolean indicating whether the live updater is running.
22     *
23     * @type {boolean}
24     * @private
25     */
26    var liveUpdaterRunning = false;
27
28    /**
29     * Set of default settings.
30     *
31     * @type {Object.<string, string>}
32     * @private
33     */
34    var defaultSettings = {
35                ellipsis: '...',
36                setTitle: 'never',
37                live: false
38            };
39
40    /**
41     * Perform ellipsis on selected elements.
42     *
43     * @param {string} selector the inner selector of elements that ellipsis may work on. Inner elements not referred to by this
44     *      selector are left untouched.
45     * @param {Object.<string, string>=} options optional options to override default settings.
46     * @return {jQuery} the current jQuery object for chaining purposes.
47     * @this {jQuery} the current jQuery object.
48     */
49    $.fn.ellipsis = function(selector, options) {
50        var subjectElements, settings;
51
52        subjectElements = $(this);
53
54        // Check for options argument only.
55        if (typeof selector !== 'string') {
56            options = selector;
57            selector = undefined;
58        }
59
60        // Create the settings from the given options and the default settings.
61        settings = $.extend({}, defaultSettings, options);
62
63        // If selector is not set, work on immediate children (default behaviour).
64        settings.selector = selector;
65
66        // Do ellipsis on each subject element.
67        subjectElements.each(function() {
68            var elem = $(this);
69
70            // Do ellipsis on subject element.
71            ellipsisOnElement(elem, settings);
72        });
73
74        // If live option is enabled, add subject elements to live updater. Otherwise remove from live updater.
75        if (settings.live) {
76            addToLiveUpdater(subjectElements.selector, settings);
77
78        } else {
79            removeFromLiveUpdater(subjectElements.selector);
80        }
81
82        // Return jQuery object for chaining.
83        return this;
84    };
85
86
87    /**
88     * Perform ellipsis on the given container.
89     *
90     * @param {jQuery} containerElement jQuery object containing one DOM element to perform ellipsis on.
91     * @param {Object.<string, string>} settings the settings for this ellipsis operation.
92     * @private
93     */
94    function ellipsisOnElement(containerElement, settings) {
95        var containerData = containerElement.data('jqae');
96        if (!containerData) containerData = {};
97
98        // Check if wrapper div was already created and bound to the container element.
99        var wrapperElement = containerData.wrapperElement;
100
101        // If not, create wrapper element.
102        if (!wrapperElement) {
103            wrapperElement = containerElement.wrapInner('<div/>').find('>div');
104
105            // Wrapper div should not add extra size.
106            wrapperElement.css({
107                margin: 0,
108                padding: 0,
109                border: 0
110            });
111        }
112
113        // Check if the original wrapper element content was already bound to the wrapper element.
114        var wrapperElementData = wrapperElement.data('jqae');
115        if (!wrapperElementData) wrapperElementData = {};
116
117        var wrapperOriginalContent = wrapperElementData.originalContent;
118
119        // If so, clone the original content, re-bind the original wrapper content to the clone, and replace the
120        // wrapper with the clone.
121        if (wrapperOriginalContent) {
122            wrapperElement = wrapperElementData.originalContent.clone(true)
123                    .data('jqae', {originalContent: wrapperOriginalContent}).replaceAll(wrapperElement);
124
125        } else {
126            // Otherwise, clone the current wrapper element and bind it as original content to the wrapper element.
127
128            wrapperElement.data('jqae', {originalContent: wrapperElement.clone(true)});
129        }
130
131        // Bind the wrapper element and current container width and height to the container element. Current container
132        // width and height are stored to detect changes to the container size.
133        containerElement.data('jqae', {
134            wrapperElement: wrapperElement,
135            containerWidth: containerElement.width(),
136            containerHeight: containerElement.height()
137        });
138
139        // Calculate with current container element height.
140        var containerElementHeight = containerElement.height();
141
142        // Calculate wrapper offset.
143        var wrapperOffset = (parseInt(containerElement.css('padding-top'), 10) || 0) + (parseInt(containerElement.css('border-top-width'), 10) || 0) - (wrapperElement.offset().top - containerElement.offset().top);
144
145        // Normally the ellipsis characters are applied to the last non-empty text-node in the selected element. If the
146        // selected element becomes empty during ellipsis iteration, the ellipsis characters cannot be applied to that
147        // selected element, and must be deferred to the previous selected element. This parameter keeps track of that.
148        var deferAppendEllipsis = false;
149
150        // Loop through all selected elements in reverse order.
151        var selectedElements = wrapperElement;
152        if (settings.selector) selectedElements = $(wrapperElement.find(settings.selector).get().reverse());
153
154        selectedElements.each(function() {
155            var selectedElement = $(this),
156                    originalText = selectedElement.text(),
157                    ellipsisApplied = false;
158
159            // Check if we can safely remove the selected element. This saves a lot of unnecessary iterations.
160            if (wrapperElement.innerHeight() - selectedElement.innerHeight() > containerElementHeight + wrapperOffset) {
161                selectedElement.remove();
162
163            } else {
164                // Reverse recursively remove empty elements, until the element that contains a non-empty text-node.
165                removeLastEmptyElements(selectedElement);
166
167                // If the selected element has not become empty, start ellipsis iterations on the selected element.
168                if (selectedElement.contents().length) {
169
170                    // If a deffered ellipsis is still pending, apply it now to the last text-node.
171                    if (deferAppendEllipsis) {
172                        getLastTextNode(selectedElement).get(0).nodeValue += settings.ellipsis;
173                        deferAppendEllipsis = false;
174                    }
175
176                    // Iterate until wrapper element height is less than or equal to the original container element
177                    // height plus possible wrapperOffset.
178                    while (wrapperElement.innerHeight() > containerElementHeight + wrapperOffset) {
179                        // Apply ellipsis on last text node, by removing one word.
180                        ellipsisApplied = ellipsisOnLastTextNode(selectedElement);
181
182                        // If ellipsis was succesfully applied, remove any remaining empty last elements and append the
183                        // ellipsis characters.
184                        if (ellipsisApplied) {
185                            removeLastEmptyElements(selectedElement);
186
187                            // If the selected element is not empty, append the ellipsis characters.
188                            if (selectedElement.contents().length) {
189                                getLastTextNode(selectedElement).get(0).nodeValue += settings.ellipsis;
190
191                            } else {
192                                // If the selected element has become empty, defer the appending of the ellipsis characters
193                                // to the previous selected element.
194                                deferAppendEllipsis = true;
195                                selectedElement.remove();
196                                break;
197                            }
198
199                        } else {
200                            // If ellipsis could not be applied, defer the appending of the ellipsis characters to the
201                            // previous selected element.
202                            deferAppendEllipsis = true;
203                            selectedElement.remove();
204                            break;
205                        }
206                    }
207
208                    // If the "setTitle" property is set to "onEllipsis" and the ellipsis has been applied, or if the
209                    // property is set to "always", the add the "title" attribute with the original text. Else remove the
210                    // "title" attribute. When the "setTitle" property is set to "never" we do not touch the "title"
211                    // attribute.
212                    if (((settings.setTitle == 'onEllipsis') && ellipsisApplied) || (settings.setTitle == 'always')) {
213                        selectedElement.attr('title', originalText);
214
215                    } else if (settings.setTitle != 'never') {
216                        selectedElement.removeAttr('title');
217                    }
218                }
219            }
220        });
221    }
222
223    /**
224     * Performs ellipsis on the last text node of the given element. Ellipsis is done by removing a full word.
225     *
226     * @param {jQuery}
226 element jQuery object containing a single DOM element.
227     * @return {boolean} true when ellipsis has been done, false otherwise.
228     * @private
229     */
230    function ellipsisOnLastTextNode(element) {
231        var lastTextNode = getLastTextNode(element);
232
233        // If the last text node is found, do ellipsis on that node.
234        if (lastTextNode.length) {
235            var text = lastTextNode.get(0).nodeValue;
236
237            // Find last space character, and remove text from there. If no space is found the full remaining text is
238            // removed.
239            var pos = text.lastIndexOf(' ');
240            if (pos > -1) {
241                text = $.trim(text.substring(0, pos));
242                lastTextNode.get(0).nodeValue = text;
243
244            } else {
245                lastTextNode.get(0).nodeValue = '';
246            }
247
248            return true;
249        }
250
251        return false;
252    }
253
254    /**
255     * Get last text node of the given element.
256     *
257     * @param {jQuery} element jQuery object containing a single element.
258     * @return {jQuery} jQuery object containing a single text node.
259     * @private
260     */
261    function getLastTextNode(element) {
262        if (element.contents().length) {
263
264            // Get last child node.
265            var contents = element.contents();
266            var lastNode = contents.eq(contents.length - 1);
267
268            // If last node is a text node, return it.
269            if (lastNode.filter(textNodeFilter).length) {
270                return lastNode;
271
272            } else {
273                // Else it is an element node, and we recurse into it.
274
275                return getLastTextNode(lastNode);
276            }
277
278        } else {
279            // If there is no last child node, we append an empty text node and return that. Normally this should not
280            // happen, as we test for emptiness before calling getLastTextNode.
281
282            element.append('');
283            var contents = element.contents();
284            return contents.eq(contents.length - 1);
285        }
286    }
287
288    /**
289     * Remove last empty elements. This is done recursively until the last element contains a non-empty text node.
290     *
291     * @param {jQuery} element jQuery object containing a single element.
292     * @return {boolean} true when elements have been removed, false otherwise.
293     * @private
294     */
295    function removeLastEmptyElements(element) {
296        if (element.contents().length) {
297
298            // Get last child node.
299            var contents = element.contents();
300            var lastNode = contents.eq(contents.length - 1);
301
302            // If last child node is a text node, check for emptiness.
303            if (lastNode.filter(textNodeFilter).length) {
304                var text = lastNode.get(0).nodeValue;
305                text = $.trim(text);
306
307                if (text == '') {
308                    // If empty, remove the text node.
309                    lastNode.remove();
310
311                    return true;
312
313                } else {
314                    return false;
315                }
316
317            } else {
318                // If the last child node is an element node, remove the last empty child nodes on that node.
319                while (removeLastEmptyElements(lastNode)) {
320                }
321
322                // If the last child node contains no more child nodes, remove the last child node.
323                if (lastNode.contents().length) {
324                    return false;
325
326                } else {
327                    lastNode.remove();
328
329                    return true;
330                }
331            }
332        }   
333
334        return false;
335    }
336
337    /**
338     * Filter for testing on text nodes.
339     *
340     * @return {boolean} true when this node is a text node, false otherwise.
341     * @this {Node}
342     * @private
343     */
344    function textNodeFilter() {
345        return this.nodeType === 3;
346    }
347
348    /**
349     * Add target selector to hash of target selectors. If this is the first target selector added, start the live
350     * updater.
351     *
352     * @param {string} targetSelector the target selector to run the live updater for.
353     * @param {Object.<string, string>} settings the settings to apply on this target selector.
354     * @private
355     */
356    function addToLiveUpdater(targetSelector, settings) {
357        // Store target selector with its settings.
358        liveUpdatingTargetSelectors[targetSelector] = settings;
359
360        // If the live updater has not yet been started, start it now.
361        if (!liveUpdaterIntervalId) {
362            liveUpdaterIntervalId = window.setInterval(function() {
363                doLiveUpdater();
364            }, 200);
365        }
366    }
367
368    /**
369     * Remove the target selector from the hash of target selectors. It this is the last remaining target selector
370     * being removed, stop the live updater.
371     *
372     * @param {string} targetSelector the target selector to stop running the live updater for.
373     * @private
374     */
375    function removeFromLiveUpdater(targetSelector) {
376        // If the hash contains the target selector, remove it.
377        if (liveUpdatingTargetSelectors[targetSelector]) {
378            delete liveUpdatingTargetSelectors[targetSelector];
379
380            // If no more target selectors are in the hash, stop the live updater.
381            if (!liveUpdatingTargetSelectors.length) {
382                if (liveUpdaterIntervalId) {
383                    window.clearInterval(liveUpdaterIntervalId);
384                    liveUpdaterIntervalId = undefined;
385                }
386            }
387        }
388    };
389
390    /**
391     * Run the live updater. The live updater is periodically run to check if its monitored target selectors require
392     * re-applying of the ellipsis.
393     *
394     * @private
395     */
396    function doLiveUpdater() {
397        // If the live updater is already running, skip this time. We only want one instance running at a time.
398        if (!liveUpdaterRunning) {
399            liveUpdaterRunning = true;
400
401            // Loop through target selectors.
402            for (var targetSelector in liveUpdatingTargetSelectors) {
403                $(targetSelector).each(function() {
404                    var containerElement, containerData;
405
406                    containerElement = $(this);
407                    containerData = containerElement.data('jqae');
408
409                    // If container element dimensions have changed, or the container element is new, run ellipsis on
410                    // that container element.
411                    if ((containerData.containerWidth != containerElement.width()) ||
412                            (containerData.containerHeight != containerElement.height())) {
413                        ellipsisOnElement(containerElement, liveUpdatingTargetSelectors[targetSelector]);
414                    }
415                });
416            }
417
418            liveUpdaterRunning = false;
419        }
420    };
421
422})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.