/* JQuery-функции */

// Класс HOVER
jQuery.fn.hoverDelayed = function(timeout, className)
{
    if (!timeout) timeout = 500;
    if (!className) className = 'hover';
    
    return this.hover(
                  function(e) { // Мышь над разделом
                      var module = $(this);
                      if (!module.hasClass(className)) // Класса HOVER нету - назначаем!
                          this.timerIN = setTimeout(function() {
                                                      module.addClass(className);
                                                   }, timeout);
                      else { // Класс HOVER уже есть - отменяем его удаление.
                          clearTimeout(this.timerOUT);
                          this.timerOUT = null;
                      }
                  },
                  function() { // Мышь покидает раздел
                      var module = $(this);
                      if (module.hasClass(className)) // Класс HOVER есть - удаляем!
                          this.timerOUT = setTimeout(function() {
                                                       module.removeClass(className);
                                                    }, timeout);
                      else { // Класса HOVER нету и не надо!
                          clearTimeout(this.timerIN);
                          this.timerIN = null;
                      }
                  });
};


/* Назначает событие, срабатывающее после изменения текстового поля (или редактируемого фрейма):
   - вставка мышью,
   - ввод с клавиатуры,
   - вырезание, вставка,
   - drag-n-drop
   - при отмене изменений в поле.
 Есть 2 варианта работы:
   1) назначение обработчика события;
   2) вызов события (как, например, стандартный JQuery - click())*/
jQuery.fn.afterChange = function(fn)
{
    return this.each(function()
    {
        var f = this; // Объект, которому назначаются события, реагирующие на изменение его содержимого
        if (typeof fn == 'function') // Нужно задать новый обработчик
        {
            // Ещё ни одна функция не задана в качестве обработчика
            if (!this._onchange)
            {
                // Единая функция-обработчик, в которой в порядке добавления вызываются функции-обработчики
                this._onchange = function(evt) {
                    var handlers = arguments.callee.handlers; // callee - ссылка на саму функцию _onchange
                    for (var i=0; i<handlers.length; i++) {
                        handlers[i].call(this, evt);
                    }
                }
                // Список функций-обработчиков (функция в JS - это объект, поэтому храним список в нём)
                this._onchange.handlers = [];
                // Вызывается единый обработчик через временную задержку (чтобы был эффект срабатывания ПОСЛЕ изменения содержимого)
                var change = function(evt) {
                    if (typeof this.old_value != 'undefined')
                    {
                        if (this.body && this.old_value==this.body.innerHTML) return;
                        if (this.old_value == this.value) return;
                    }
                    setTimeout(
                        function() {f._onchange(evt);},
                        50
                    );
                    if (this.body) this.old_value = this.body.innerHTML;
                    else this.old_value = this.value;
                };
                //// Назначаем обработчики события
                // document в режиме DesignMode=on (ContentEditable=true)
                if (this.body) 
                {
                    // К сожалению, в Опере не работают (на момент написания) события onpaste и oncut
                    $(this).bind('keyup paste cut drop mouseup', change);
                }
                // INPUT и TEXTAREA
                else if (this.tagName=='INPUT' || this.tagName=='TEXTAREA')
                {
                    if (typeof this.onpaste=='object') // для IE
                    {
                        $(this).bind('keyup paste cut drop', change);
                    }
                    else // для других браузеров
                    {
                        $(this).bind('input drop', change); 
                    }
                }
            }
            // Добавляем функцию в очередь обработчиков события afterChange
            this._onchange.handlers.push(fn);
        }
        // Инициирование события afterChange
        else if (typeof fn == 'undefined' && this._onchange)
            this._onchange();
   });
}
// bestChange - клон функции afterChange для мест, где еще не заменили
jQuery.fn.bestChange = jQuery.fn.afterChange;

// Улучшенная функция jQuery slideDown()
jQuery.fn.slideDown2 = function(time, callback)
{
    return this.each(function()
    {
        if ($(this).is(':visible')) return;
        
        var s = this.style;
        
        // Запоминаем margin-top и margin-bottom и сбрасываем в 0
        var mt = parseInt($(this).css('margin-top')) || 0;
        var mb = parseInt($(this).css('margin-bottom')) || 0;
        s.marginTop = 0;
        s.marginBottom = 0;
        
        // Запоминаем padding-top и padding-bottom и сбрасываем в 0
        var pt = parseInt($(this).css('padding-top')) || 0;
        var pb = parseInt($(this).css('padding-bottom')) || 0;
        s.paddingTop = 0;
        s.paddingBottom = 0;
        
        // Анимацию высоты верхнего и нижнего бордера не делаем,
        // так как в стандартной функции это не реализовано.
        
        // Вычисляем ширину элемента
        s.overflow = 'hidden';
        s.height = 0;
        s.display = 'block';
        var w = this.offsetWidth;
        
        // Вычисляем высоту
        s.width = w+'px';
        s.position = 'absolute';
        s.visibility = 'hidden';
        s.height = '';
        var h = this.offsetHeight;
        // Вычитаем из высоты border-top-width и border-bottom-width (чтобы не было скачка)
        h = h - (parseInt($(this).css('border-top-width')) || 0);
        h = h - (parseInt($(this).css('border-bottom-width')) || 0);
        
        // Восстанавливаем до нулевых размеров
        s.overflow = '';
        s.height = 0;
        s.width = '';
        s.position = '';
        s.visibility = '';
        
        $(this).animate(
            {
                height: h+'px',
                marginTop: mt+'px',
                marginBottom: mb+'px',
                paddingTop: pt+'px',
                paddingBottom: pb+'px'
            },
            time,
            function()
            {
                s.height = '';
                s.marginTop = '';
                s.marginBottom = '';
                s.paddingTop = '';
                s.paddingBottom = '';
                if (typeof callback == 'function')
                    callback.call(this);
            }
        );
    });
}

// Так как slideToggle основана на slideDown и slideUp (точнее на таких же алгоритмах),
// то создадим и функцию slideToggle2:
jQuery.fn.slideToggle2 = function(time, callback)
{
    return this.each(function()
    {
        if ($(this).is(':visible'))
            $(this).slideUp(time, callback);
        else $(this).slideDown2(time, callback);
    });
}

/*
    Убирает серую подсказку из поля ввода, если элемент получил фокус,
    и устанавливает её, если элемент потерял фокус.
    focused_height - высота элемента, когда он в фокусе.
        При задании необходимо указывать размерность (10px, 20em и т.д.)
        допустимо указывать 0, если не нужно изменять высоту
    focusAction, blurAction - функции, которые будут вызываться при получении и потере фокуса соответственно (можно не указывать).
*/
jQuery.fn.hintable = function(focused_height, focusAction, blurAction)
{
    return this.each(function()
    {
        var no_active_text = $(this).attr('title');
        $(this).removeAttr('title');
        $(this).val(no_active_text);
        
        var no_active_height = 0;
        
        $(this).focus(function()
        {
            if ($(this).hasClass('greyed'))
            {
                $(this).removeClass('greyed').val('');
                
                if (focused_height)
                {
                    if (!no_active_height)
                        no_active_height = $(this).height(); // Запомнили прежнюю высоту
                    $(this).css('height', focused_height);
                }
                
                if (typeof focusAction == 'function')
                {
                    focusAction.call(this);
                }
                
                if (typeof blurAction == 'function')
                {
                    blurAction.call(this);
                }
            }
        })
        
        $(this).blur(function()
        {
            if (!$.trim($(this).val()))
            {
                $(this).addClass('greyed').val(no_active_text);
                if (no_active_height)
                {
                    $(this).height(no_active_height);
                }
            }
        })
    });
}

/*
  При написании текста в textarea автоматически увеличивает или уменьшает высоту в соответствии с количеством текста.
*/
jQuery.fn.autoHeight = function(class_name, min_height)
{
    if (typeof class_name != 'string' || !min_height)
        return this;
    
    return this.each(function()
    {
        var clone = $(this).clone();
        clone.css('position', 'absolute')
             .css('left', '-5000px')
             .css('visibility', 'hidden')
             .css('height', '0')
             .removeAttr('class')
             .removeAttr('id')
             .addClass(class_name)
             .insertBefore(this);
             
        $(this).afterChange(function()
        {
            $(this).removeClass('greyed'); // На всякий случай
            
            var h = $(this).height();
            
            if (this.scrollHeight > this.offsetHeight)
                $(this).height(this.scrollHeight)
            else
            {
                clone.val($(this).val());
                h = clone.get(0).scrollHeight;
                if (h < min_height) h = min_height;
                $(this).height(h);
            }
        });
    })
}
      
// Автоматическое скрывание элементов после убирания мышки
/* TODO:
   - скрытие только требуемых элементов
   - отображение после скрытия всех?
*/
jQuery.fn.autoHide = function()
{
    if (typeof arguments[0] == 'string')
    {
        var hidded = arguments[0];
        var time = arguments[1];
        var callback = arguments[2];
        var conditionFN = arguments[3];
    }
    else
    {
        var hidded = null;
        var time = arguments[0];
        var callback = arguments[1];
        var conditionFN = arguments[2];
    }

    if (!time) time = 5000;
    
    var t = this;
    var self = arguments.callee;
    if (!self.elems) self.elems = [];
    
    self.elems.push({
        'elems':this,
        'hidded':hidded,
        'time':time,
        'callback':callback,
        'conditionFN':conditionFN
    });
    this.hover(
        function(){$(this).addClass('_hover');},
        function(){$(this).removeClass('_hover');}
    );
    
    if (!self.timer)
    {
        self.timer = setInterval(function()
        {
            for (var i=0,t,n=self.elems.length; i<n; i++)
            {
                t = self.elems[i];
                for (var j=0,m=t.elems.length; j<m; j++)
                {
                    var e = t.elems.get(j);
                    
                    var h = (t.hidded ? $(t.hidded, e).get(0) : e);
                    if (!h) continue;
                    
                    var extra = (
                        !$(e).hasClass('_hover') &&
                        $(h).is(':visible') &&
                        !$(h).is(':animated') &&
                        !(typeof t.conditionFN == 'function' && !t.conditionFN.call(e))
                    );
                    
                    if (!e.hide_timer && extra)
                    {
                        (function (e, h, t)
                        {
                            e.hide_timer = setTimeout(
                                function()
                                {
                                    e.hide_timer = null;
                                    
                                    // !extra (но нужно перепроверить)
                                    if ($(e).hasClass('_hover') ||
                                        !$(h).is(':visible') ||
                                        $(h).is(':animated') ||
                                        typeof t.conditionFN == 'function' && !t.conditionFN.call(e))
                                    {
                                        return;
                                    }

                                    $(h).slideUp(500, function()
                                    {
                                        if (typeof t.callback == 'function')
                                            t.callback.call(h);
                                    });
                                },
                                t.time
                            );
                        })(e, h, t)
                    }
                    else if (e.hide_timer && !extra)
                    {
                        clearTimeout(e.hide_timer);
                        e.hide_timer = null;
                    }
                }    
            }
        }, 500);
        
        self.hideAll = function(not)
        {
            not = $(not).get(0);
            for (var i=0,t,n=self.elems.length; i<n; i++)
            {
                t = self.elems[i];
                for (var j=0,m=t.elems.length; j<m; j++)
                {
                    var e = t.elems.get(j);
                    
                    var h = (t.hidded ? $(t.hidded, e).get(0) : e);
                    if (!h) continue;
                    
                    var extra = (
                        !$(e).hasClass('_hover') &&
                        $(h).is(':visible') &&
                        !(typeof t.conditionFN == 'function' && !t.conditionFN.call(e)) &&
                        not!=h && not!=e
                    );
                    
                    if (extra)
                    {
                        if (e.hide_timer)
                        {
                            clearTimeout(e.hide_timer);
                            e.hide_timer = null;
                        }

                        (function(e, h, t)
                        {
                            $(h).slideUp(500, function()
                            {
                                if (typeof t.callback == 'function')
                                    t.callback.call(h);
                            });
                        }
                        (e, h, t))
                    }
                }    
            }
        }
    }
    return this;
}
$.autoHideAll = function(not){
    if (typeof $.fn.autoHide.hideAll=='function')
        $.fn.autoHide.hideAll(not);
}
/* конец JQuery-функций */

function trim(str) {
        return str.replace(/^\s+|\s+$/g, '');
}


function hash_empty(hash) {
        // Javascript не имеет средств для определения
        // того, не пуст ли ассоциативный массив
        // так что пришлось реализовывать вручную
        var empty = true;
        for (var i in hash) { empty = false; break; }
        return empty;
}


function checkemail(str) {
        var re = /^([\w\-\.]+)@([\w\-]+)(\.)([\w]+)/ig;
        if ( re.test(str) ) {
                /* Gecko RegExp Fix --> */ re.test(str);
                return true;
         } else return false;
}


function remove_slideup(tag) {
    if ( $(tag) ) { 
        $(tag).slideUp(
                        'slow',
                        function() { $(tag).remove(); }
                );
    }
    return false;
}

// Находит позицию курсора в текстовом поле
function getCaretPos(f)
{
    if (!f.length) return false;
    f = f.get(0);

    var cur = 0;
    if (typeof f.selectionStart != 'undefined') // FF, Opera, Chrome
    {
        cur = f.selectionStart;
    } else if (document.selection) // IE
    {
        f.focus();
        var Sel = document.selection.createRange();
         Sel.moveStart('character', -f.value.length);
        cur = Sel.text.length;
    }
    return cur;
}

// Усанавливает курсор в нужную позицию тектового поля
function setCaretPos(f, pos)
{
    if (!f.length) return false;
    f = f.get(0);

    if(f.setSelectionRange) // FF, Opera, Chrome
    {
        f.focus();
        f.setSelectionRange(pos, pos);
    }
    else if (f.createTextRange) { // IE
        var range = f.createTextRange();
        range.collapse(true);
        range.moveEnd('character', pos);
        range.moveStart('character', pos);
        range.select();
    }
}

function clean_input_symbols(field, regexp) {
    var regexp_def = /[^\wа-яА-ЯЁё. ,:!?-]/g;

    if (regexp == undefined)
        regexp = regexp_def;

    var str = field.val();
    if (str.search(regexp) != -1)
    {
        var newStr = str.replace(regexp, '');
        var newPos = getCaretPos(field) - (str.length - newStr.length);
        field.val(newStr);
        setCaretPos(field, newPos);
    }
}

function wordforms(n, forms) {
    var n100 = n % 100;
    var n10 = n % 10;
    
    if (n100 > 10 && n100 < 20) i = 2;
    else if (n10 == 1) i = 0;
    else if (n10 >= 2 && n10 <= 4) i = 1;
    else i = 2;
    
    return forms[i];
}


function resize_rectangle(width, height, max) {
    // аналог resize_rectangle() из general_functions.php
    // функция принимает стороны прямоугольника 
    // и максимальный размер квадрата, 
    // в который прямоугольник вписан
    // например, прямоугольник размеров 800x600
    // для вписывания в квадрат 100x100 должен стать 75x100
    
    if (!height) return [max, 0];
    
    var ratio = width/height;
    var w, h;
    if (ratio > 1) {
        w = max;
        h = Math.round(max/ratio);
    }
    else {
        w = Math.round(max*ratio);
        h = max;
    }
    
    return [w, h];
}


function input_length(field, max){
    var len = (field.val()).length;
    //alert(field.val());
    // для textarea
    if(len > max){
        str = field.val().substr(0, max);
        field.val(str);
        len = max;
    }
    
    var str = 'символов';
    var diff = max - len;
    
    str = wordforms(diff, ['символ', 'символа', 'символов']);
    
    return diff + ' ' + str;
}

// BB-code
var bb_array = [];
function bb(id) {
    if (typeof(bb_array[id]) == "undefined") {
        bb_array[id] = new bb_obj(id);
    }
    return bb_array[id];
}
function bb_obj(id) {
    var obj = document.getElementById(id);
    this.target = obj;
    this.target.carretHandler = this;
    this.target.onchange = _textareaSaver;
    this.target.onclick = _textareaSaver;
    this.target.onkeyup = _textareaSaver;
    this.target.onfocus = _textareaSaver;
    if(document.attachEvent) this.target.onselect = _textareaSaver;
    this.start=-1;
    this.end=-1;
    this.scroll=-1;
    this.iesel=null;
}
bb_obj.prototype = {
    get : function() {
        return this.iesel? this.iesel.text: (this.start>=0&&this.end>this.start)? this.target.value.substring(this.start,this.end): "";
    },
    set : function(text, secondtag) {
        if (this.iesel) {
            if (typeof(secondtag) == "string") {
                var l = this.iesel.text.length;
                this.iesel.text = text + this.iesel.text + secondtag;
                this.iesel.moveEnd("character", -secondtag.length);
                this.iesel.moveStart("character", -l);
            } else {
                this.iesel.text = text;
            }
            this.iesel.select();
        } else if (this.start >= 0 && this.end >= this.start) {
            var left = this.target.value.substring(0, this.start);
            var right = this.target.value.substr(this.end);
            var scont = this.target.value.substring(this.start, this.end);
            if (typeof(secondtag) == "string") {
                this.target.value = left + text + scont + secondtag + right;
                this.end = this.target.selectionEnd=this.start+text.length+scont.length;
                this.start = this.target.selectionStart = this.start + text.length;
            } else {
                this.target.value = left + text + right;
                this.end = this.target.selectionEnd = this.start + text.length;
                this.start = this.target.selectionStart = this.start + text.length;
            }
            this.target.scrollTop = this.scroll;
            this.target.focus();
        } else {
            this.target.value += text + ((typeof(secondtag) == "string") ? secondtag: "");
            if (this.scroll >= 0) this.target.scrollTop = this.scroll;
        }
    }
}
function _textareaSaver() {
    if(document.selection) {
        this.carretHandler.iesel = document.selection.createRange().duplicate();
    } else if(typeof(this.selectionStart) != "undefined") {
        this.carretHandler.start = this.selectionStart;
        this.carretHandler.end = this.selectionEnd;
        this.carretHandler.scroll = this.scrollTop;
    } else {
        this.carretHandler.start = this.carretHandler.end = -1;
    }
}



function lockPage(show) {
        var div, id = 'lockPageId';
        if ( !$('#lockPageId').length ) {
                div = document.createElement('div');
                div.id = id;
                $(div)
                        .css('position', 'absolute')
                        .css('z-index', 1000)
                        .css('background', '#666')
                        .css('top', '0')
                        .css('left', '0')
                        .css('opacity', 0.2)
                ;
                $(document.body).append(div);
        }
        if (show) {
                $('#lockPageId')
                        .css('display', 'block')
                        .css('width', $(document.body).width())
                        .css('height', $(document.body).height() + 100)
                ;
        } else
                $('#lockPageId').css('display', 'none');
        return false;
}


function urlEncode(str) {
    if (!str || typeof(str) == 'undefined') return;
    var utf8Array = {};
    var i = j = j2 = 0;
    for (i = 0; i <= 255; i++) {
        j = parseInt(i/16); var j2 = parseInt(i%16);
        utf8Array[String.fromCharCode(i)] = ('%' + j.toString(16) + j2.toString(16)).toUpperCase();
    }
    var rusAdditional = {
        '_' : '%5F', 'А' : '%C0', 'Б' : '%C1', 'В' : '%C2', 'Г' : '%C3', 'Д' : '%C4', 'Е' : '%C5',
        'Ж' : '%C6', 'З' : '%C7', 'И' : '%C8', 'Й' : '%C9', 'К' : '%CA', 'Л' : '%CB', 'М' : '%CC',
        'Н' : '%CD', 'О' : '%CE', 'П' : '%CF', 'Р' : '%D0', 'С' : '%D1', 'Т' : '%D2', 'У' : '%D3',
        'Ф' : '%D4', 'Х' : '%D5', 'Ц' : '%D6', 'Ч' : '%D7', 'Ш' : '%D8', 'Щ' : '%D9', 'Ъ' : '%DA',
        'Ы' : '%DB', 'Ь' : '%DC', 'Э' : '%DD', 'Ю' : '%DE', 'Я' : '%DF', 'а' : '%E0', 'б' : '%E1',
        'в' : '%E2', 'г' : '%E3', 'д' : '%E4', 'е' : '%E5', 'ж' : '%E6', 'з' : '%E7', 'и' : '%E8',
        'й' : '%E9', 'к' : '%EA', 'л' : '%EB', 'м' : '%EC', 'н' : '%ED', 'о' : '%EE', 'п' : '%EF',
        'р' : '%F0', 'с' : '%F1', 'т' : '%F2', 'у' : '%F3', 'ф' : '%F4', 'х' : '%F5', 'ц' : '%F6',
        'ч' : '%F7', 'ш' : '%F8', 'щ' : '%F9', 'ъ' : '%FA', 'ы' : '%FB', 'ь' : '%FC', 'э' : '%FD',
        'ю' : '%FE', 'я' : '%FF', 'ё' : '%B8', 'Ё' : '%A8'
    }
    for (i in rusAdditional) utf8Array[i] = rusAdditional[i];
    var res = '';
    for(i = 0; i < str.length; i++) {
        var simbol = str.substr(i,1);
        res += typeof utf8Array[simbol] != 'undefined' ? utf8Array[simbol] : simbol;
    }
    res = res.replace(/\s/g, '+');
    return res;
}


function build_select( 
    tag, // css/jquery-идентификатор тэга
    sort_array, // массив со значениями value в порядке сортировки 
    data_array, // массив со строковыми значениями для списка
    active_element, // value выбранного, активного элемента
    blank_text // текст default-элемента
    ) {
    // July 2010 by 1234ru
    // делаем копии переданных массивов
    // т.к. в процессе работы функции 
    // их придется поменять
    var sort = ( 
            (sort_array != undefined) 
            ? sort_array.slice() 
            : new Array
            );
    var data = data_array.slice();
    
    if (blank_text != undefined) {
        // unshift сбивает значения ключей массива
        // (увеличивает на 1)
        // поэтому его можно использовать только для сортировки
        // (где важно лишь, чтобы сохранился порядок)
        // но категорически нельзя для данных
        // т.к. там собьётся соответствие key=>value
        
        sort.unshift(0);
        data[0] = blank_text;
    }
    
    $(tag + ' option').remove();
    
    var value, text;
    
    for (var key in sort) {
        // document.write(key + ' ' + data[key] + '<br/>');
        value = sort[key];
        text = data[value];
        // alert(text + ' ' + value + '<br/>');
        var opt = document.createElement('option');
        $(opt).text(text);
        $(opt).val(value);
        if (value == active_element) { $(opt).attr('selected','selected'); }
        $(tag).append(opt);
    }
}


function build_select_old(tagid, data_array, active) { 
    /* Apr 2010 by 1234ru*/
    // tagid нужно передавать с #
    // проверим, пустой ли массив
    // (идиотская архитектура Javascript'а)
    for (var key in data_array) { var notempty = 1; break; }
    if (notempty == undefined) return '';
    $(tagid + ' option').remove();
    for (var key in data_array) {
        //document.write(active);
        var opt = document.createElement('option');
        $(opt).text(data_array[key]);
        $(opt).val(key);
        if (key == active) { $(opt).attr('selected','selected'); }
        $(tagid).append(opt);
    }
}


// Определение версии браузера
function getBrowser()
{
    var ua = navigator.userAgent;
    
    var msie = false;
    var version = 0;
    
    // TODO: Добавить для остальных браузеров
    
    // ### Для IE
    var MSIEOffset = ua.indexOf("MSIE ");
    if (MSIEOffset != -1)
    {
        msie = true;
        version = parseFloat(ua.substring(MSIEOffset + 5, ua.indexOf(";", MSIEOffset)));
    }
    
    return {
        msie: msie,
        version: version
    };
}

function ie6_hover(elem)
{
    if (getBrowser().msie && getBrowser().version<7)
    {
        try
        {
            $(elem).hover(
                function() {$(this).addClass('hover');},
                function() {$(this).removeClass('hover');}
                );
        } catch(e) {}
    }
}

// Добавить в Избранное
function add_favorite(a) {
  title=document.title;
  url=document.location;
  try {
    // Internet Explorer
    window.external.AddFavorite(url, title);
  }
  catch (e) {
    try {
      // Mozilla
      window.sidebar.addPanel(title, url, "");
    }
    catch (e) {
      // Opera
      if (typeof(opera)=="object") {
        a.rel="sidebar";
        a.title=title;
        a.url=url;
        return true;
      }
      else {
        // Unknown
        alert('Нажмите Ctrl-D чтобы добавить страницу в закладки');
      }
    }
  }
  return false;
}

function shortText(text, len, end_text, delim)
{
    if (end_text == undefined) end_text = true;
    if (!delim) delim = " \r\n\t:;.…,?!#—+-=()[]{}";
    text = $.trim(text);
    if (len < text.length) // Если строка длиннее len, то производим обрезку текста text
    {
        for (var i = len-1; i>=0; i--) // Ищем от (i-1)-го символа до начала текста правую границу слова
            if (delim.indexOf(text[i]) == -1 && delim.indexOf(text[i+1]) != -1)
                break; // правая граница найдена
        text = text.substring(0, (i>=0 ? i+1 : len));
        text = text + (end_text ? '...' : '');
    }
    return text;
}
