Greasy Fork is available in English.

Include Tools

Общие инструменты для всех страничек

此脚本不应被直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.org/scripts/379902/1071128/Include%20Tools.js

// ==UserScript==
// @name           Include Tools
// @namespace      scriptomatika
// @author         mouse-karaganda
// @description    Общие инструменты для всех страничек
// @version        1.35
// @grant          none
// ==/UserScript==

/* jshint esversion: 6 */

var paramWindow = (typeof unsafeWindow === 'object') ? unsafeWindow : window;

(function(unsafeWindow) {
    var console = unsafeWindow.console;
    var jQuery = unsafeWindow.jQuery;

    unsafeWindow.__krokodil = {
        /**
         * Отрисовывает элемент
         */
        renderElement: function(config) {
            // Определяем параметры по умолчанию
            var newRenderType = this.setRenderType(config.renderType);
            var newConfig = {
                // ~~~ название тега ~~~ //
                tagName: config.tagName || 'div',
                // ~~~ атрибуты тега ~~~ //
                attr: config.attr || {},
                // ~~~ идентификатор ~~~ //
                id: config.id,
                // ~~~ имя класса ~~~ //
                cls: config.cls,
                // ~~~ встроенные стили тега ~~~ //
                style: config.style || {},
                // ~~~ встроенные данные ~~~ //
                dataset: config.dataset || {},
                // ~~~ содержимое элемента ~~~ //
                innerHTML: this.join(config.innerHTML || ''),
                // ~~~ обработчики событий элемента ~~~ //
                listeners: config.listeners || {},
                // ~~~ родительский элемент для нового ~~~ //
                renderTo: this.getIf(config.renderTo),
                // ~~~ способ отрисовки:  append (по умолчанию), insertBefore, insertAfter, insertFirst, none ~~~ //
                renderType: newRenderType
            };
            var newElement;
            if (newConfig.tagName == 'text') {
                // Создаем текстовый узел
                newElement = document.createTextNode(newConfig.innerHTML);
            } else {
                // Создаем элемент
                newElement = document.createElement(newConfig.tagName);
                // Добавляем атрибуты
                this.attr(newElement, newConfig.attr);
                // Добавляем идентификатор, если указан
                if (newConfig.id) {
                    this.attr(newElement, { 'id': newConfig.id });
                }
                //console.log('newElement == %o, config == %o, id == ', newElement, newConfig, newConfig.id);
                // Добавляем атрибут класса
                if (newConfig.cls) {
                    this.attr(newElement, { 'class': newConfig.cls });
                }
                // Наполняем содержимым
                newElement.innerHTML = newConfig.innerHTML;
                // Задаем стиль
                this.css(newElement, newConfig.style);
                // Задаем встроенные данные
                this.extend(newElement.dataset, newConfig.dataset);
                // Навешиваем события
                var confListeners = newConfig.listeners;
                for (var ev in confListeners) {
                    if (ev != 'scope') {
                        //console.log('this.on(newElement == %o, ev == %o, newConfig.listeners[ev] == %o, newConfig.listeners.scope == %o)', newElement, ev, newConfig.listeners[ev], newConfig.listeners.scope);
                        this.on(newElement, ev, newConfig.listeners[ev], newConfig.listeners.scope);
                    }
                }
                //console.log('После: tag == %o, listeners == %o', newConfig.tagName, confListeners);
            }
            // Отрисовываем элемент
            var target, returnRender = true;
            while (returnRender) {
                switch (newConfig.renderType) {
                    // Не отрисовывать, только создать
                    case this.enumRenderType['none']: {
                        returnRender = false;
                        break;
                    };
                    // Вставить перед указанным
                    case this.enumRenderType['insertBefore']: {
                        target = newConfig.renderTo || document.body.firstChild;
                        // если элемент не задан - вернемся к способу по умолчанию
                        if (target) {
                            target.parentNode.insertBefore(newElement, target);
                            returnRender = false;
                        } else {
                            newConfig.renderType = this.enumRenderType['default'];
                        }
                        break;
                    };
                    // Вставить после указанного
                    case this.enumRenderType['insertAfter']: {
                        // если элемент не задан - вернемся к способу по умолчанию
                        if (newConfig.renderTo && newConfig.renderTo.nextSibling) {
                            target = newConfig.renderTo.nextSibling;
                            target.parentNode.insertBefore(newElement, target);
                            returnRender = false;
                        } else {
                            newConfig.renderType = this.enumRenderType['default'];
                        }
                        break;
                    };
                    // Вставить как первый дочерний
                    case this.enumRenderType['prepend']: {
                        // если элемент не задан - вернемся к способу по умолчанию
                        if (newConfig.renderTo && newConfig.renderTo.firstChild) {
                            target = newConfig.renderTo.firstChild;
                            target.parentNode.insertBefore(newElement, target);
                            returnRender = false;
                        } else {
                            newConfig.renderType = this.enumRenderType['default'];
                        }
                        break;
                    };
                    // Вставить как последний дочерний
                    case this.enumRenderType['append']:
                    default: {
                        var parent = newConfig.renderTo || document.body;
                        parent.appendChild(newElement);
                        returnRender = false;
                    };
                }
            }
            // Возвращаем элемент
            return newElement;
        },
        /**
         * Отрисовать несколько одинаковых элементов подряд
         */
        renderElements: function(count, config) {
            for (var k = 0; k < count; k++) {
                this.renderElement(config);
            }
        },
        /**
         * Отрисовать текстовый узел
         */
        renderText: function(config) {
            // Упрощенные настройки
            var newConfig = {
                tagName: 'text',
                innerHTML: config.text,
                renderTo: config.renderTo,
                renderType: config.renderType
            };
            var newElement = this.renderElement(newConfig);
            return newElement;
        },
        /**
         * Отрисовать элемент style
         * @param {String} text Любое количество строк через запятую
         */
        renderStyle: function(text) {
            var stringSet = arguments;
            var tag = this.renderElement({
                tagName: 'style',
                attr: { type: 'text/css' },
                innerHTML: this.format('\n\t{0}\n', this.join(stringSet, '\n\t'))
            });
            return tag;
        },
        /**
         * Возможные способы отрисовки
         */
        enumRenderType: {
            'append': 0,
            'prepend': 1,
            'insertBefore': 2,
            'insertAfter': 3,
            'none': 4,
            'default': 0
        },
        // Назначает способ отрисовки
        setRenderType: function(renderType) {
            if (typeof renderType != 'string') {
                return this.enumRenderType['default'];
            }
            if (this.enumRenderType[renderType] == undefined) {
                return this.enumRenderType['default'];
            }
            return this.enumRenderType[renderType];
        },
        /**
         * Карта кодов клавиш
         */
        keyMap: {
            // Клавиши со стрелками
            arrowLeft: 37,
            arrowUp: 38,
            arrowRight: 39,
            arrowDown: 40
        },
        /**
         * Карта кодов символов
         */
        charMap: {
            arrowLeft: 8592, // ←
            arrowRight: 8594 // →
        },
        /**
         * Ждём, пока отрисуется элемент, и выполняем действия
         * @param {String} selector css-селектор для поиска элемента (строго строка)
         * @param {Function} callback Функция, выполняющая действия над элементом. this внутри неё — искомый DOM-узел
         * @param {Number} maxIterCount Максимальное количество попыток найти элемент
         */
        missingElement: function(selector, callback, maxIterCount) {
            var setLog = false;
            // Итерации 10 раз в секунду
            var missingOne = 100;
            // Ограничим количество попыток разумными пределами
            var defaultCount = 3000;
            if (!this.isNumber(maxIterCount)) {
                maxIterCount = defaultCount;
            }
            if (0 > maxIterCount || maxIterCount > defaultCount) {
                maxIterCount = defaultCount;
            }
            // Запускаем таймер на поиск
            var iterCount = 0;
            var elementTimer = setInterval(this.createDelegate(function() {
                // Сообщение об ожидании
                var showIter = (iterCount % 10 == 0);
                showIter &= (300 > iterCount) || (iterCount > 2700);
                if (showIter) {
                    var secondsMsg = this.numberWithCase(iterCount, 'секунду', 'секунды', 'секунд');
                    if (setLog) console.log('missing: Ждём [%o] %s', selector, secondsMsg);
                }
                var element = this.getAll(selector);
                // Определим, что вышел элемент
                var elementStop = this.isIterable(element) && (element.length > 0);
                // Определим, что кончилось количество попыток
                var iterStop = (iterCount >= maxIterCount);
                if (elementStop || iterStop) {
                    clearInterval(elementTimer);
                    var elementExists = true;
                    // Если элемент так и не появился
                    if (!elementStop && iterStop) {
                        if (setLog) console.log('missing: Закончились попытки [%o]', selector);
                        elementExists = false;
                        return;
                    }
                    // Появился элемент - выполняем действия
                    if (setLog) console.log('missing: Появился элемент [%o] == %o', selector, element);
                    if (this.isFunction(callback)) {
                        if (element.length == 1) {
                            element = element[0];
                        }
                        if (setLog) console.log('missing: Запускаем обработчик [%o] == %o', elementExists, element);
                        callback.call(element, elementExists);
                    }
                }
                iterCount++;
            }, this), missingOne);
        },
        /**
         * Добавить свойства в объект
         */
        extend: function(target, newProperties) {
            if (typeof newProperties == 'object') {
                for (var i in newProperties) {
                    target[i] = newProperties[i];
                }
            }
            return target;
        },
        /**
         * Создать класс-наследник от базового класса или объекта
         */
        inherit: function(base, newConfig) {
            var newProto = (typeof base == 'function') ? new base() : this.extend({}, base);
            this.extend(newProto, newConfig);
            return function() {
                var F = function() {};
                F.prototype = newProto;
                return new F();
            };
        },
        /**
         * Получить элемент по селектору
         */
        get: function(selector, parent) {
            parent = this.getIf(parent);
            return (parent || unsafeWindow.document).querySelector(selector);
        },
        /**
         * Получить массив элементов по селектору
         */
        getAll: function(selector, parent) {
            parent = this.getIf(parent);
            return (parent || unsafeWindow.document).querySelectorAll(selector);
        },
        /**
         * Получить элемент, если задан элемент или селектор
         */
        getIf: function(element) {
            return this.isString(element) ? this.get(element) : element;
        },
        /**
         * Получить массив элементов, если задан массив элементов или селектор
         */
        getIfAll: function(elements) {
            return this.isString(elements) ? this.getAll(elements) : this.toIterable(elements);
        },
        /**
         * Назначим атрибуты элементу или извлечем их
         */
        attr: function(element, attributes) {
            var nativeEl = this.getIf(element);
            if (typeof attributes == 'string') {
                // извлечем атрибут
                var result = '';
                if (nativeEl.getAttribute) {
                    result = nativeEl.getAttribute(attributes);
                }
                if (!result) {
                    result = '';
                }
                return result;
            } else if (typeof attributes == 'object') {
                // назначим атрибуты всем элементам по селектору
                nativeEl = this.getIfAll(element);
                for (var i = 0; i < nativeEl.length; i++) {
                    // назначим атрибуты из списка
                    for (var at in attributes) {
                        try {
                            if (attributes[at] == '') {
                                // Удалим пустой атрибут
                                nativeEl[i].removeAttribute(at);
                            } else {
                                // Запишем осмысленный атрибут
                                nativeEl[i].setAttribute(at, attributes[at]);
                            }
                        } catch (e) {
                            console.error(e);
                        }
                    }
                }
            }
        },
        /**
         * Назначим стили элементу или извлечем их
         */
        css: function(element, properties) {
            var nativeEl = this.getIf(element);
            if (typeof properties == 'string') {
                // извлечем стиль
                var result = '';
                if (nativeEl.style) {
                    var calcStyle = window.getComputedStyle(nativeEl, null) || nativeEl.currentStyle;
                    result = calcStyle[properties];
                }
                if (!result) {
                    result = '';
                }
                return result;
            } else if (typeof properties == 'object') {
                // присвоим стили всем элементам по селектору
                nativeEl = this.getIfAll(element);
                try {
                    for (var i = 0; i < nativeEl.length; i++) {
                        // назначим стили из списка
                        this.extend(nativeEl[i].style, properties);
                    }
                } catch (e) {
                    console.error(e);
                }
            }
        },
        /**
         * Показать элемент
         */
        show: function(element, inline) {
            var current = this.getIf(element);
            if (current) {
                var style = current.style;
                style.display = inline ? 'inline' : 'block';
            }
        },
        /**
         * Спрятать элемент
         */
        hide: function(element, soft) {
            var current = this.getIf(element);
            if (current) {
                if (!!soft) {
                    current.style.visibility = 'hidden';
                } else {
                    current.style.display = 'none';
                }
            }
        },
        /**
         * Спрятать элемент, убрав его за границу экрана
         */
        hideFixed: function(element) {
            var current = this.getIf(element);
            if (current) {
                this.css(current, {
                    position: 'fixed',
                    left: '-2000px',
                    top: '-2000px'
                });
            }
        },
        /**
         * Удалить элемент
         */
        del: function(element) {
            var current = this.getIf(element);
            if (current && current.parentNode) {
                current.parentNode.removeChild(current);
            }
        },
        /**
         * Изменить видимость элемента
         */
        toggle: function(element, inline) {
            this.isVisible(element) ? this.hide(element) : this.show(element, inline);
        },
        /**
         * Проверить, виден ли элемент
         */
        isVisible: function(element) {
            return this.getIf(element).style.display != 'none';
        },
        /**
         * Навесить обработчик
         */
        on: function(element, eventType, handler, scope) {
            var elements;
            if (!element) {
                return false;
            }
            if (this.isString(element)) {
                element = this.getIfAll(element);
                if (!(element && this.isIterable(element)))
                    return false;
            }
            if (!this.isIterable(element)) {
                element = this.toIterable(element);
            }
            var eventHandler = handler;
            if (scope) {
                eventHandler = this.createDelegate(handler, scope, handler.arguments);
            }
            this.each(element, function(currentEl) {
                if (currentEl.addEventListener) {
                    currentEl.addEventListener(eventType, eventHandler, false);
                }
                else if (currentEl.attachEvent) {
                    currentEl.attachEvent('on' + eventType, eventHandler);
                }
            }, this);
        },
        /**
         * Запустить событие
         */
        fireEvent: function(element, eventType, keys, bubbles, cancelable) {
            // Определим необходимые параметры
            var eventBubbles = this.isBoolean(bubbles) ? bubbles : true;
            var eventCancelable = this.isBoolean(cancelable) ? cancelable : true;
            // Для клика создадим MouseEvent
            var isMouse = /click|dblclick|mouseup|mousedown/i.test(eventType);
            // Приведем к нужному виду клавиши
            keys = keys || {};
            this.each(['ctrlKey', 'altKey', 'shiftKey', 'metaKey'], function(letter) {
                if (!keys[letter]) {
                    keys[letter] = false;
                }
            });
            // запустим для всех элементов по селектору
            var nativeEl = this.getIfAll(element);
            this.each(nativeEl, function(elem) {
                var evt = document.createEvent(isMouse ? 'MouseEvents' : 'HTMLEvents');
                if (isMouse) {
                    // Событие мыши
                    // event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
                    evt.initMouseEvent(eventType, eventBubbles, eventCancelable, window, 0, 0, 0, 0, 0, keys.ctrlKey, keys.altKey, keys.shiftKey, keys.metaKey, 0, null);
                } else {
                    // Событие общего типа
                    // event.initEvent(type, bubbles, cancelable);
                    evt.initEvent(eventType, eventBubbles, eventCancelable);
                }
                //var evt = (isMouse ? new MouseEvent() : new UIEvent());
                elem.dispatchEvent(evt);
                //console.log('dispatchEvent elem == %o, event == %o', elem, evt);
            }, this);
        },
        /**
         * Остановить выполнение события
         */
        stopEvent: function(e) {
            var event = e || window.event;
            if (!event) {
                return false;
            }
            event.preventDefault = event.preventDefault || function() {
                this.returnValue = false;
            };
            event.stopPropagation = event.stopPropagation || function() {
                this.cancelBubble = true;
            };
            event.preventDefault();
            event.stopPropagation();
            return true;
        },
        /**
         * Выделить текст в поле ввода
         */
        selectText: function(element, start, end) {
            var current = this.getIf(element);
            if (!current) {
                return;
            }
            if (!end) {
                end = start;
            }
            // firefox
            if ('selectionStart' in element) {
                element.setSelectionRange(start, end);
                element.focus(); // to make behaviour consistent with IE
            }
            // ie win
            else if(document.selection) {
                var range = element.createTextRange();
                range.collapse(true);
                range.moveStart('character', start);
                range.moveEnd('character', end - start);
                range.select();
            }
        },
        /**
         * Определяет, является ли значение строкой
         */
        isString : function(v) {
            return typeof v === 'string';
        },
        /**
         * Определяет, является ли значение числом
         */
        isNumber: function(v) {
            return typeof v === 'number' && isFinite(v);
        },
        /**
         * Определяет, является ли значение булевым
         */
        isBoolean: function(v) {
            return typeof v === 'boolean';
        },
        /**
         * Определяет, является ли значение объектом
         */
        isObject : function(v) {
            return typeof v === 'object';
        },
        /**
         * Определяет, является ли значение функцией
         */
        isFunction: function(v) {
            return typeof v === 'function';
        },
        /**
         * Определяет, является ли значение датой
         */
        isDate: function(v) {
            var result = true;
            this.each([
                'getDay',
                'getMonth',
                'getFullYear',
                'getHours',
                'getMinutes'
            ], function(property) {
                result == result && this.isFunction(v[property]);
            }, this);
            return result;
        },
        /**
         * Переведем число в удобочитаемый вид с пробелами
         */
        numberToString: function(v) {
            var partLen = 3;
            try {
                v = Number(v);
            } catch (e) {
                return v;
            }
            v = String(v);
            var pointPos;
            pointPos = (pointPos = v.indexOf('.')) > 0 ? (pointPos) : (v.length);
            var result = v.substring(pointPos);
            v = v.substr(0, pointPos);
            var firstPart = true;
            while (v.length > 0) {
                var startPos = v.length - partLen;
                if (startPos < 0) {
                    startPos = 0;
                }
                if (!firstPart) {
                    result = ' ' + result;
                }
                firstPart = false;
                result = v.substr(startPos, partLen) + result;
                v = v.substr(0, v.length - partLen);
            }
            return result;
        },
        /**
         * Число с текстом в нужном падеже
         * @param {Number} number Число, к которому нужно дописать текст
         * @param {String} textFor1 Текст для количества 1
         * @param {String} textFor2 Текст для количества 2
         * @param {String} textFor10 Текст для количества 10
         */
        numberWithCase: function(number, textFor1, textFor2, textFor10) {
            // Определяем, какой текст подставить, по последней цифре
            var lastDigit = number % 10;
            var result = {
                number: number,
                text: ''
            };
            // Текст для количества 1
            if (this.inArray(lastDigit, [ 1 ])) {
                result.text = textFor1;
            }
            // Текст для количества 2
            if (this.inArray(lastDigit, [ 2, 3, 4 ])) {
                result.text = textFor2;
            }
            // Текст для количества 10
            if (this.inArray(lastDigit, [ 5, 6, 7, 8, 9, 0 ])) {
                result.text = textFor10;
            }
            // Текст для количества от 11 до 19
            var twoLastDigits = number % 100;
            if (10 < twoLastDigits && twoLastDigits < 20) {
                result.text = textFor10;
            }
            return this.template('{number} {text}', result);
        },
        /**
         * Определить, является ли тип значения скалярным
         */
        isScalar: function(v) {
            return this.isString(v) || this.isNumber(v) || this.isBoolean(v);
        },
        /**
         * Определить, является ли тип значения перечислимым
         */
        isIterable: function(v) {
            var result = !!v;
            if (result) {
                result = result && this.isNumber(v.length);
                result = result && !this.isString(v);
                // У формы есть свойство length - пропускаем её
                result = result && !(v.tagName && v.tagName.toUpperCase() == 'FORM');
            }
            return result;
        },
        /**
         * Сделать значение перечислимым
         */
        toIterable: function(value) {
            if (!value) {
                return value;
            }
            return this.isIterable(value) ? value : [value];
        },
        /**
         * Задать область видимости (scope) для функции
         */
        createDelegate: function(func, scope, args) {
            var method = func;
            return function() {
                var callArgs = args || arguments;
                return method.apply(scope || window, callArgs);
            };
        },
        /**
         * Проверим, является ли значение элементом массива или объекта
         */
        inArray: function(value, array) {
            return this.each(array, function(key) {
                if (key === value) {
                    return true;
                }
            }) !== true;
        },
        /**
         * Найдем значение в массиве и вернем индекс
         */
        findInArray: function(value, array) {
            var result = this.each(array, function(key) {
                if (key === value) {
                    return true;
                }
            });
            return this.isNumber(result) ? result : -1;
        },
        /**
         * Запустить функцию для всех элементов массива или объекта
         * @param {Array} array Массив, в котором значения будут перебираться по индексу элемента
         * @param {Object} array Объект, в котором значения будут перебираться по имени поля
         * @returns {Number} Индекс элемента, на котором досрочно завершилось выполнение, если array - массив
         * @returns {String} Имя поля, на котором досрочно завершилось выполнение, если array - объект
         * @returns {Boolean} True, если выполнение не завершалось досрочно
         */
        each: function(array, fn, scope) {
            if (!array) {
                return;
            }
            if (this.isIterable(array)) {
                for (var i = 0, len = array.length; i < len; i++) {
                    if (this.isBoolean( fn.call(scope || array[i], array[i], i, array) )) {
                        return i;
                    };
                }
            } else {
                for (var key in array) {
                    if (this.isBoolean( fn.call(scope || array[key], array[key], key, array) )) {
                        return key;
                    };
                }
            }
            return true;
        },
        /**
         * Разбить строку, укоротив её и склеив части указанным разделителем
         * @param {String} original Исходная строка
         * @param {Number} maxLength Максимальная длина, до которой нужно усечь исходную строку
         * @param {Number} tailLength Длина второй короткой части
         * @param {String} glue Разделитель, который склеит две части укороченной строки
         */
        splitWithGlue: function(original, maxLength, tailLength, glue) {
            // Разделитель по умолчанию
            if (!this.isString(glue)) {
                glue = '...';
            }
            // По умолчанию строка завершается разделителем
            if (!this.isNumber(tailLength)) {
                tailLength = 0;
            }
            var result = original;
            if (result.length > maxLength) {
                result = this.template('{head}{glue}{tail}', {
                    head: original.substring(0, maxLength - (tailLength + glue.length)),
                    glue: glue,
                    tail: original.substring(original.length - tailLength)
                });
            }
            return result;
        },
        /**
         * форматирование строки, используя объект
         */
        template: function(strTarget, objSource) {
            var s = arguments[0];
            for (var prop in objSource) {
                var reg = new RegExp("\\{" + prop + "\\}", "gm");
                s = s.replace(reg, objSource[prop]);
            }
            return s;
        },
        /**
         * форматирование строки, используя числовые индексы
         */
        format: function() {
            var original = arguments[0];
            this.each(arguments, function(sample, index) {
                if (index > 0) {
                    var currentI = index - 1;
                    var reg = new RegExp("\\{" + currentI + "\\}", "gm");
                    original = original.replace(reg, sample);
                }
            });
            return original;
        },
        /**
         * Быстрый доступ к форматированию
         */
        fmt: function() {
            return this.format.apply(this, arguments);
        },
        /**
         * Выдать строку заданной длины с заполнением символом
         */
        leftPad: function (val, size, character) {
            var result = String(val);
            if (!character) {
                character = ' ';
            }
            while (result.length < size) {
                result = character + result;
            }
            return result;
        },
        /**
        * Определить, какая часть окна ушла вверх при прокрутке
        */
        getScrollOffset: function () {
            var d = unsafeWindow.top.document;
            return top.pageYOffset ? top.pageYOffset : (
                (d.documentElement && d.documentElement.scrollTop) ? (d.documentElement.scrollTop) : (d.body.scrollTop)
            );
        },
        /**
        * Определить размер окна
        */
        getWindowSize: function () {
            var d = unsafeWindow.top.document;
            return {
                width: /*top.innerWidth ? top.innerWidth :*/ (
                    (d.documentElement.clientWidth) ? (d.documentElement.clientWidth) : (d.body.offsetWidth)
                ),
                height: /*top.innerHeight ? top.innerHeight :*/ (
                    (d.documentElement.clientHeight) ? (d.documentElement.clientHeight) : (d.body.offsetHeight)
                )
            };
        },
        /**
         * Склеить строки
         */
        join: function(rows, glue) {
            return Array.prototype.slice.call(this.toIterable(rows), 0).join(glue || '');
        },
        /**
         * Вернем значение cookie
         */
        getCookie: function(name) {
            var value = null;
            // Проверим, есть ли кука с таким именем
            var cookie = unsafeWindow.document.cookie;
            var regKey = new RegExp(name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=(.*?)((; ?)|$)');
            var hasMatch = cookie.match(regKey);
            if (hasMatch && hasMatch[1]) {
                value = decodeURIComponent(hasMatch[1]);
            }
            return value;
        },
        /**
         * Установим значение cookie
         * @param {Object} options Объект с дополнительными значениями
         * - expires Срок действия куки: {Number} количество дней или {Data} дата окончания срока
         * - path Путь, отсчитывая от которого будет действовать кука
         * - domain Домен, в пределах которого будет действовать кука
         * - secure Кука для https-соединения
         */
        setCookie: function(name, value, options) {
            // Можно опустить значение куки, если нужно удалить
            if (!value) {
                value = '';
            }
            options = options || {};
            // Проверяем, задана дата или количество дней
            var expires = '';
            if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
                var date;
                if (typeof options.expires == 'number') {
                    date = new Date();
                    date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
                } else {
                    date = options.expires;
                }
                expires = '; expires=' + date.toUTCString();
            }
            // Проставляем другие опции
            var path = options.path ? '; path=' + (options.path) : '';
            var domain = options.domain ? '; domain=' + (options.domain) : '';
            var secure = (options.secure === true) ? '; secure' : '';
            unsafeWindow.document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
        },
        /**
         * Картинка с большим пальцем
         */
        getThumbHand: function() {
            var thumbSource;
            thumbSource = ( // рука
'data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA0UlEQVR42r3SPwsBYRzA8buSlMFi\
MymDd+A1WEiSUDarwS6TwSyjgUkkfxZh4J0YpQwKk8L36R56uu5Rd1ee+izXPd/nN/xMw+cx/xXI\
ooYxhm4DSbRRxAQ5N4EUmqjKKZ4YOAXmeCjfj1ICddyxwVVGxL0dep+AGK2gBA5oYPZjuoWYSheY\
Iq+52EUMAWS8BHxNUJbfo9ij5XWCEl4Y6QIrpG2X4uggjIh84KQLnFHB2uH1kGHtglis7x5scVF+\
uom6Ye3ByxYIoo+lGvB8fAfecvkwEbIZfswAAAAASUVORK5CYII=');
            thumbSource = ( // сообщение
'data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABL0lEQVQ4y2P4//8/AyWYgWoGRLTv\
EALipUD8E4j/48G7gFgFmwEbVx689f/3n7//8YEtJ++DDLkNxGxwA4AcHiD+8/ffv/8fvv37//LT\
v//PPv77/+T9v/8P3/37f+/Nv/+3X/39f+cVxPDqBcdBhpghGyCXM/UAWPIFUOOzD//+PwZqfvD2\
3/+7UM3XX/z9f/UZxIDOVWdBBnhjNeApUPOjd1DNr//9v/USovkKUPPFJ7gNgHsB5Pz7QFvvvP77\
/yZQ87Xnf/9fBmq+8ARkwB+wAbWLToAMsMQaiCBDkAHINRce/wUbjBaInLii8Q8syubuuAo36P3n\
H2A+UPwy1mjEhoEK7zx/9xWm8TsQ1xKdEoGKe2duuwLS+AWIC0lKykANSkB8D4hT6JcXBswAAPeL\
DyK+4moLAAAAAElFTkSuQmCC');
            return thumbSource;
        },
        /**
         * Отладка
         */
        thumb: function(text) {
            var bgImage = this.format('background: url("{0}") no-repeat;', this.getThumbHand());
            console.log('%c   ', bgImage, text);
        },
        /**
         * Удалим значение cookie
         */
        removeCookie: function(name) {
            this.setCookie(name, null, { expires: -1 });
        },
        /**
         * Отладка
         */
        groupDir: function(name, object) {
            console.group(name);
            console.dir(object);
            console.groupEnd();
        },
        /**
         * Отладка: ошибка с пользовательским сообщением
         */
        errorist: function(error, text, parameters) {
            var params = Array.prototype.slice.call(arguments, 1);
            params.unshift('#FFEBEB');
            this.coloredLog(params);
            console.error(error);
        },
        /**
         * Отладка: вывод цветной строки
         */
        coloredLog: function(color, text) {
            var params = Array.prototype.slice.call(arguments, 2);
            params.unshift('background-color: ' + color + ';');
            params.unshift('%c' + text);
            console.log.apply(console, params);
        },
        /**
         * XPath-запрос
         */
        xpath: function(selector) {
            var nodes = document.evaluate(selector, document, null, XPathResult.ANY_TYPE, null);
            var thisNode = nodes.iterateNext();
            while (thisNode) {
                //console.log(thisNode.textContent);
                thisNode = nodes.iterateNext();
            }
        },
        /**
         * Упаковать для хранилища
         */
        packToStorage: function(objBox) {
            var clone = this.extend({}, objBox);
            this.each(clone, function(property, index) {
                if (typeof property == 'function') {
                    clone[index] = property.toString();
                }
                if (typeof property == 'object') {
                    clone[index] = this.packToStorage(property);
                }
            }, this);
            return JSON.stringify(clone);
        },
        /**
         * Распаковать из хранилища
         */
        unpackFromStorage: function(objBox) {
            var result = {};
            try {
                result = JSON.parse(objBox);
            } catch (e) {
                try {
                    result = eval('(' + objBox + ')');
                } catch (e) {
                    result = objBox;
                }
            }
            if (typeof result == 'object') {
                for (var property in result) {
                    result[property] = this.unpackFromStorage(result[property]);
                }
            }
            return result;
        },
        /**
         * Проверить соответствие домена, как для Stylish
         */
        mozDocumentDomainIs: function() {
            let result = false;
            let domainList = Array.prototype.slice.call(arguments, 0);
            this.each(domainList, function(domainName, index) {
                let current = (document.domain == domainName) || (document.domain.substring(document.domain.indexOf(domainName) + 1) == domainName);
                result |= current;
            });
            return result;
        },
        /**
         * Проверить начало URL, как для Stylish
         */
        mozDocumentUrlPrefixIs: function() {
            let result = false;
            let prefixList = Array.prototype.slice.call(arguments, 0);
            this.each(prefixList, function(prefix, index) {
                let current = (document.location.href.indexOf(prefix) == 0);
                result |= current;
            });
            return result;
        }
    };

    // Добавляем обратный порядок в jQuery
    if (typeof jQuery != 'undefined') {
        if (typeof jQuery.fn.reverse != 'function') {
            jQuery.fn.reverse = function() {
                return jQuery(this.get().reverse());
            };
        }
        if (typeof jQuery.fn.softHide != 'function') {
            jQuery.fn.softHide = function() {
                return jQuery(this).css({ visibility: 'hidden' });
            };
        }
    }

    unsafeWindow.NodeList.prototype.size = () => this.length;

    // форматирование строки
    unsafeWindow.String.prototype.format = unsafeWindow.__krokodil.format;

    //отладка
    unsafeWindow.console.groupDir = unsafeWindow.__krokodil.groupDir;
    unsafeWindow.console.coloredLog = unsafeWindow.__krokodil.coloredLog;
    unsafeWindow.console.errorist = unsafeWindow.__krokodil.errorist;

    //unsafeWindow.__krokodil.thumb('Include Tools');
    //console.coloredLog('#fffbd6', 'Include Tools');
    //console.errorist('Include Tools');
    console.log('Include Tools 💬 1.35');

})(paramWindow);