Внезапно: новая версия ONLYOFFICE с макросами

    У нас важные новости: вышла новая версия десктопов с макросами. Вы можете скачать её на официальном сайте и попробовать всё сами.

    В этой статье мы расскажем, что у нас за макросы, чем они отличаются от макросов Microsoft и как с ними работать.



    Как у нас сделаны макросы?


    Макрос — это скрипт, с помощью которого можно автоматизировать рутинные операции и сэкономить целую кучу времени. Само понятие придумала компания Microsoft, поэтому эти скрипты предназначаются для Microsoft Office и работают на Windows.

    «Когда вы сделаете макросы?» — это очень популярный вопрос. Поначалу мы отвечали: «Никогда. Но мы можем сделать то же самое с помощью плагинов» (и делали).

    Так вот, за год наша система плагинов стала гораздо круче (и более подробно документированной). Теперь мы сами добавляем новую функциональность с помощью плагинов. Например, Symbol Table: в редакторах документов нужны спецсимволы, и добавить их через плагины в разы быстрее. Кроме того, у нас уже был готовый SDK для генерации и обработки документов, электронных таблиц и презентаций — ONLYOFFICE Document Builder.

    В общем, когда речь в очередной раз зашла о макросах, мы поняли, что у нас всё сложилось. И сложилось довольно красиво. Мы берем Document Builder, оборачиваем его в плагин и пробрасываем в интерфейс. Теперь у нас есть возможность создавать и запускать макросы!



    Сейчас макросы умеют делать всё, что умеет делать Builder. Документацию к нему можно найти тут. Отдельную документацию по макросам уже пишем.

    Наши макросы не такие, как у Microsoft


    Если вы видели наши плагины, то должны догадаться, в чём оно заключается, сразу же. В том, что вы будете писать не на Visual Basic, а на JavaScript.

    «Почему не Visual Basic, а как же совместимость?!»

    Предвидим этот вопрос и отвечаем на него заранее. Во-первых, VBA только для Windows, а у нас тут все платформы. Если бы у Microsoft был какой-то скриптовый язык, мы бы с радостью его поддержали. Но миллион лет писать компилятор очень старого языка и в результате получить макросы (такие же как у MS) — это не задача мечты. Мы предпочтем в этом время совершенствовать редакторы. К тому же, у нас всё готово с js.

    Это классно, потому что:

    — Будет работать со всеми платформами;
    — Реально очень просто;
    — Написав толковый скрипт, вы можете использовать его и как плагин, и как макрос. Ну и само собой как скрипт для Document Builder'а, конечно же.

    Про последний пункт: макрос — это, по сути, частный случай плагина. Просто установленные плагины работают для всех документов (то есть, привязаны к редакторам), а макросы — только конкретного документа (то есть, привязаны к файлу).

    Так что всё-таки с совместимостью?


    Да, макросы из Microsoft так просто не откроются у нас. В то же время мы понимаем, что у многих уже есть целые библиотеки макросов, которые хотелось бы запустить в альтернативном офисе.

    Пока мы можем предложить самый простой вариант: немного переписать макросы, написанные на VBA. Понимаем, возможно, это прозвучит как головная боль, но на самом деле это совсем несложно.

    Вот пример заполнения нескольких ячеек данными (суммирование диапазона). Первый — вариант MS с Visual Basic, второй — наш.

    Первый пример:

    Sub Example()
        Dim myRange
        Dim result
        Dim Run As Long
     
        For Run = 1 To 3
            Select Case Run
            Case 1
                result = "=SUM(A1:A100)"
            Case 2
                result = "=SUM(A1:A300)"
            Case 3
                result = "=SUM(A1:A25)"
            End Select
            ActiveSheet.range("B" & Run) = result
        Next Run
    End Sub


    Второй:

    (function()
    {
        for (let run = 1; run <= 3; run++)
        {
            var result = "";
            switch (run)
            {
                case 1:
                    result = "=SUM(A1:A100)";
                    break;
                case 2:
                    result = "=SUM(A1:A300)";
                    break;
                case 3:
                    result = "=SUM(A1:A25)";
                    break;
                default:
                    break;
            }
            
            Api.GetActiveSheet().GetRange("B" + run).Value = result;
        }
    })();


    Как видите, это довольно просто.

    А теперь крутой пример:

    Здесь у нас длинный скрипт
    (function()
    {
    var oSheet = Api.GetActiveSheet();
    oSheet.SetName('Medal Number');
    
    oSheet.SetColumnWidth(0, 7.57);
    oSheet.SetColumnWidth(1, 12.43);
    oSheet.SetColumnWidth(2, 32.50);
    oSheet.SetColumnWidth(3, 13.86);
    oSheet.SetColumnWidth(4, 13.86);
    oSheet.SetColumnWidth(5, 13.86);
    oSheet.SetColumnWidth(6, 13.86);
    
    var range = oSheet.GetRange('C1');
    range.SetFontSize(56);
    
    range = oSheet.GetRange('B2:G29');
    range.SetFontName('Calibri');
    range.SetFontSize(13);
    range.SetFontColor(Api.CreateColorFromRGB(0, 0, 0));
    range.SetAlignHorizontal('center');
    
    oSheet.GetRange('B2').SetValue('Rank');
    oSheet.GetRange('C2').SetValue('Country');
    oSheet.GetRange('D2').SetValue('Gold');
    oSheet.GetRange('E2').SetValue('Silver');
    oSheet.GetRange('F2').SetValue('Bronze');
    oSheet.GetRange('G2').SetValue('Total');
    
    for (var nCell = 0; nCell < 25; ++nCell)
    {
    oValue = nCell + 1;
    oCellNumber = nCell + 3;
    oSheet.GetRange('B' + oCellNumber.toString()).SetValue(oValue.toString());
    }
    
    oSheet.GetRange('C3:C27').SetAlignHorizontal('left');
    oSheet.GetRange('C3').SetValue('USA');
    oSheet.GetRange('C4').SetValue('China');
    oSheet.GetRange('C5').SetValue('Great Britain');
    oSheet.GetRange('C6').SetValue('Russia');
    oSheet.GetRange('C7').SetValue('Germany');
    oSheet.GetRange('C8').SetValue('Japan');
    oSheet.GetRange('C9').SetValue('France');
    oSheet.GetRange('C10').SetValue('South Korea');
    oSheet.GetRange('C11').SetValue('Italy');
    oSheet.GetRange('C12').SetValue('Australia');
    oSheet.GetRange('C13').SetValue('Netherlands');
    oSheet.GetRange('C14').SetValue('Hungary');
    oSheet.GetRange('C15').SetValue('Brazil');
    oSheet.GetRange('C16').SetValue('Spain');
    oSheet.GetRange('C17').SetValue('Kenya');
    oSheet.GetRange('C18').SetValue('Jamaica');
    oSheet.GetRange('C19').SetValue('Croatia');
    oSheet.GetRange('C20').SetValue('Cuba');
    oSheet.GetRange('C21').SetValue('New Zealand');
    oSheet.GetRange('C22').SetValue('Canada');
    oSheet.GetRange('C23').SetValue('Uzbekistan');
    oSheet.GetRange('C24').SetValue('Kazakhstan');
    oSheet.GetRange('C25').SetValue('Colombia');
    oSheet.GetRange('C26').SetValue('Switzerland');
    oSheet.GetRange('C27').SetValue('Iran');
    
    oSheet.GetRange('D3').SetValue('46');
    oSheet.GetRange('D4').SetValue('27');
    oSheet.GetRange('D5').SetValue('26');
    oSheet.GetRange('D6').SetValue('19');
    oSheet.GetRange('D7').SetValue('17');
    oSheet.GetRange('D8').SetValue('12');
    oSheet.GetRange('D9').SetValue('10');
    oSheet.GetRange('D10').SetValue('9');
    oSheet.GetRange('D11').SetValue('8');
    oSheet.GetRange('D12').SetValue('8');
    oSheet.GetRange('D13').SetValue('8');
    oSheet.GetRange('D14').SetValue('8');
    oSheet.GetRange('D15').SetValue('7');
    oSheet.GetRange('D16').SetValue('7');
    oSheet.GetRange('D17').SetValue('6');
    oSheet.GetRange('D18').SetValue('6');
    oSheet.GetRange('D19').SetValue('5');
    oSheet.GetRange('D20').SetValue('5');
    oSheet.GetRange('D21').SetValue('4');
    oSheet.GetRange('D22').SetValue('4');
    oSheet.GetRange('D23').SetValue('4');
    oSheet.GetRange('D24').SetValue('3');
    oSheet.GetRange('D25').SetValue('3');
    oSheet.GetRange('D26').SetValue('3');
    oSheet.GetRange('D27').SetValue('3');
    
    oSheet.GetRange('E3').SetValue('37');
    oSheet.GetRange('E4').SetValue('23');
    oSheet.GetRange('E5').SetValue('18');
    oSheet.GetRange('E6').SetValue('18');
    oSheet.GetRange('E7').SetValue('10');
    oSheet.GetRange('E8').SetValue('8');
    oSheet.GetRange('E9').SetValue('18');
    oSheet.GetRange('E10').SetValue('3');
    oSheet.GetRange('E11').SetValue('12');
    oSheet.GetRange('E12').SetValue('11');
    oSheet.GetRange('E13').SetValue('7');
    oSheet.GetRange('E14').SetValue('3');
    oSheet.GetRange('E15').SetValue('6');
    oSheet.GetRange('E16').SetValue('4');
    oSheet.GetRange('E17').SetValue('6');
    oSheet.GetRange('E18').SetValue('3');
    oSheet.GetRange('E19').SetValue('3');
    oSheet.GetRange('E20').SetValue('2');
    oSheet.GetRange('E21').SetValue('9');
    oSheet.GetRange('E22').SetValue('3');
    oSheet.GetRange('E23').SetValue('2');
    oSheet.GetRange('E24').SetValue('5');
    oSheet.GetRange('E25').SetValue('2');
    oSheet.GetRange('E26').SetValue('2');
    oSheet.GetRange('E27').SetValue('1');
    
    oSheet.GetRange('F3').SetValue('38');
    oSheet.GetRange('F4').SetValue('17');
    oSheet.GetRange('F5').SetValue('26');
    oSheet.GetRange('F6').SetValue('19');
    oSheet.GetRange('F7').SetValue('15');
    oSheet.GetRange('F8').SetValue('21');
    oSheet.GetRange('F9').SetValue('14');
    oSheet.GetRange('F10').SetValue('9');
    oSheet.GetRange('F11').SetValue('8');
    oSheet.GetRange('F12').SetValue('10');
    oSheet.GetRange('F13').SetValue('4');
    oSheet.GetRange('F14').SetValue('4');
    oSheet.GetRange('F15').SetValue('6');
    oSheet.GetRange('F16').SetValue('6');
    oSheet.GetRange('F17').SetValue('1');
    oSheet.GetRange('F18').SetValue('2');
    oSheet.GetRange('F19').SetValue('2');
    oSheet.GetRange('F20').SetValue('4');
    oSheet.GetRange('F21').SetValue('5');
    oSheet.GetRange('F22').SetValue('15');
    oSheet.GetRange('F23').SetValue('7');
    oSheet.GetRange('F24').SetValue('9');
    oSheet.GetRange('F25').SetValue('3');
    oSheet.GetRange('F26').SetValue('2');
    oSheet.GetRange('F27').SetValue('4');
    
    for (var nCell = 0; nCell < 25; ++nCell)
    {
    oCellNumber = nCell + 3;
    oSheet.GetRange('G' + oCellNumber.toString()).SetValue('=SUM(D' + oCellNumber.toString() + ':F' + oCellNumber.toString() + ')');
    }
    
    oSheet.GetRange('C29').SetValue('Total:');
    oSheet.GetRange('C29').SetAlignHorizontal('right');
    oSheet.GetRange('D29').SetValue('=SUM(D3:D27)');
    oSheet.GetRange('E29').SetValue('=SUM(E3:E27)');
    oSheet.GetRange('F29').SetValue('=SUM(F3:F27)');
    oSheet.GetRange('G29').SetValue('=SUM(G3:G27)');
    oSheet.GetRange('D29:F29').SetFontColor(Api.CreateColorFromRGB(67, 67, 67));
    oSheet.GetRange('G29').SetFontColor(Api.CreateColorFromRGB(49, 133, 154));
    oSheet.GetRange('C29:G29').SetFontSize(14);
    
    oSheet.FormatAsTable('B2:G29');
    
    var oChart = oSheet.AddChart("'Medal Number'!$C$2:$F$27", false, 'barStacked3D', 18, 8, 1, 16, 14);
    oChart.SetVerAxisTitle("Medals", 10);
    oChart.SetHorAxisTitle("Countries", 10);
    oChart.SetLegendPos("right");
    oChart.SetShowDataLabels(false, false, false);
    oChart.SetTitle("Total Medal Count", 18);
    
    var oChart2 = oSheet.AddChart("'Medal Number'!$C$2:$E$12", false, 'lineStacked', 2, 8, 15, 16, 27);
    oChart2.SetVerAxisTitle("Medals", 10);
    oChart2.SetHorAxisTitle("Top 10 Countries", 10);
    oChart2.SetLegendPos("right");
    oChart2.SetShowDataLabels(false, false, false);
    oChart2.SetTitle("Gold&Silver Medals Count", 18);
    })();


    В результате у вас должна получиться вот такая красота:

    image

    Вы можете скачать десктопное приложение ONLYOFFICE и всё попробовать. Кстати, макросы не единственное новшество вышедшей версии. Мы много всего пофиксили, добавили поддержку SSO, новые языки интерфейса (чешский и словацкий). Подробная информация о новой версии на GitHub.

    Это всё. Ждем ваших вопросов, предложений, пожеланий и мыслей. Если у вас есть интересные файлы с макросами, которыми вы можете поделиться, присылайте на files@onlyoffice.com. Если у вас есть файлы без макросов, но с интересными проблемами и ошибками, ждем их тоже.
    ONLYOFFICE 54,62
    Компания
    Поделиться публикацией
    Комментарии 44
    • +1
      А когда пофиксите размер заголовка окна, он занимает слишком много места.
      Скриншот
      image
      • +1
        Какой размер монитора и разрешение? Мы сами решаем, как масштабировать интерфейс. Но странно, что внутренность не увеличена, а только шапка приложения.
        • +1
          14" 1920×1080. Никаких перенастроек размера элементов на экране не делал (scale and layout 100%). Что интересно, если подключить внешний монитор и перетащить окно на него, то все становится нормально, перетаскиваем назад и опять большое.
          • +1
            Спасибо, будем разбираться. Тестировали на похожем ноутбуке — все было отлично. Посмотрю еще. Масштаб системы мы не учитываем. Ориентируемся на dpi устройства. И при переносе с монитора с на монитор — анализируем новый.
            • +1
              Windows 10 по умолчанию, считает такое разрешение недопустимым для таких маленьких экранов и автоматически увеличивает DPI. Столкнулся с этим на своем ноутбуке. Вообще 10-ка, довольно свободно себя чувтствует с переключением DPI не оповещая об этом пользователя, если понимает, что текст будет мелким и плохо читаемым.
              • +1
                Это да. Из-за этого много проблем. Например имея два монитора — 4K c двойным масштабированием системы и обычный монитор — даже MS Office мутный при перетаскивании на другой монитор. Но мы запрещаем скейлить наще приложение. И сами все рассчитываем и решаем как скейлить интерфейс и сами редакторы. И у нас перетаскивается отлично. Не можем повторить пока этот баг.
                • +1
                  Не знаю, поможет или нет, все это на Dell Latitude E5450 с Windows 10
                  • +1
                    Спасибо! Нашел проблему. С обновлением поправится.
                  • +1

                    А, ну вот, решаете сами, а лучше бы читали переменные окружения, как умеет делать Qt.

                    • +3
                      Qt неправильно работает с переносом окон на мониторы с разным масштабированием системы. Так что мы уж точно делаем лучше. А баги мы поправим. Тем более у нас в QWidget встраивается свое окно, которое знать не знает о том, как использует настройки системы Qt.
              • +1

                Аналогичная проблема. Планшет Chuwi Hi12, экран HiDPI 2K, KDE, Ubuntu 17.10, в настройках KDE нецелочисленное масштабирование, допустим, 1.8. Onlyoffice открывается маленьким окном. Chromium, Qt5 масштабируются правильно.

                • +1
                  Да. В новой версии есть баг с масштабом в Ubuntu. Он заведен и будет поправлен к следующему обновлению
            • +1
              Конвертер макросов из VBA в JavaScript не планируете делать?
              • +1
                Планируем.
                • +1
                  А из JS в VBA тоже планируете?
                  • +1
                    Основное — это конечно открыть файл. Пока нет.
              • +1
                Крутой пример того, что не следует делать макросами)
                • +2
                  Это пример, демонстрирующий возможности. Вместо добавления данных может быть использование существующих и работа с ними / редактирование их. Пойдет?
                • +1
                  Хотелось бы узнать, почему из всех возможных вариантов был выбран именно JS?
                  • +3
                    Самый распространенный язык. И один из самых простых. И есть веб-версия редактора еще.
                    • +1
                      Веб-версия — весомый аргумент. Есть ли совместимость с макросами Google Docs?
                      • +1
                        Нет, совместимости нет, не ставили такую задачу. Все, кто обращался — просили VBA. А делать точно такое апи как в гуглдокс — не стали.
                        • +1
                          Ясно, благодарю за ответ.
                  • +1
                    У меня с интеграцией сложности как раз с nextcloud. Указываю локальный адрес типа 192.168.100.100/nextcloud, но подключение не происходит. В какие логи смотреть?
                    • +2
                      Интеграция с nextcloud описана здесь и здесь. Если остались вопросы — то обратитесь сюда. Мы обязательно поможем.
                      • +1
                        Спасибо большое, еще раз перечитаю мануал.
                        • +1
                          Кажется понял свою ошибку. Насколько корректно будет, если я вынесу сервер OnlyOffice на отдельную виртуальную машину, чтобы изолировать максимально от NextCloud?
                          • +2
                            Получится отличный рабочий вариант.
                            Главное, чтоб они друг друга видели (сейчас при подключении сервера документов в админке nextcloud есть проверка доступности с обеих сторон, если есть проблемы с доступом, nextcloud предупредит об этом).
                            Особенно это удобно, когда один сервер документов используют одновременно несколько Next(own)Cloud'ов и им подобных.
                            • +1
                              Спасибо, я стараюсь просто изолировать крупные сервисы отдельно друг от друга.
                      • +1
                        Просто установленные плагины работают для всех документов (то есть, привязаны к редакторам), а макросы — только конкретного документа (то есть, привязаны к файлу).


                        Я храню макросы в файлах
                        C:\Users\%user%\AppData\Roaming\Microsoft\Excel\XLSTART\PERSONAL.XLSB
                        C:\Users\%user%\Documents\Templates\Normal.dotm
                        И они доступны для всех документов.
                        Не совсем понятна область видимости ваших макросов в зависимости от местоположения таковых макросов.

                        oSheet.GetRange('D29').SetValue('=SUM(D3:D27)');


                        Тут несколько сомнений:
                        • Привык работать в нотации R1C1 в листе и с номерами колонок-столбцов из макросов. Эти вот FW190 меня обескураживают. И это не говоря о том, что это строковая константа, которую надо будет вручную нанизывать скриптом.
                        • Неясно как быть с локализацией — вдруг окажется, что в некоем документе будет или надо будет писать =СУММА(Г3: Г27)


                        А из JS в VBA тоже планируете? — Пока нет

                        Это был бы неплохой ответ на вопрос о Microsoft макросах — налету сконвертить в js, исполнить. При изменении сконвертить обратно. С сообщениями о несовместимости некоторых команд.
                        • +1
                          Можно писать и СУММА и SUM. Но мы будем настаивать на английском варианте в макросах. Конвертация — да. Круто бы. Будем работать, но задача не первоочередная.
                          • +1
                            «Привык работать в нотации R1C1 в листе и с номерами колонок-столбцов из макросов»

                            Так MS Excel умеет в обоих нотациях работать, включается-выключается галочкой и формулы сами переписываются в другой нотации. Думаю что это можно предложить и для OnlyOffice если говорить про формулы.
                            Если про макросы — то указания на клетки для обработки должны быть в полной нотации, т.к. нет начальной точки откуда считать. Т.е. в части oSheet.GetRange('D29').SetValue непонятно как можно в R1C1 записать адрес.
                            А формулы внутри можно в любой.
                            • +1
                              Это R4C29
                              Нотация:
                              R4 — всегда четвёртая колонка;
                              R — текущая колонка;
                              R[-4] — колонка на 4 левее.

                              With Worksheets(1) 
                                  .Range(.Cells(1, 1), .Cells(10, 10)).Borders.LineStyle = xlThick 
                              End With
                              • +1
                                Добавим возможность такой записи.
                                • +1
                                  Верно
                                  .Cells(1,1) — очень хорошо для макросов, я сам так пользуюсь
                                  А вот формулы в листе писать в RC довольно сложно, ибо если ориентироваться на «R4C29», то это абсолютная ссылка, при протягивании она не будет меняться. Если делать относительной, то там будет вид RC[5], и чтобы понять куда иммено ссылается надо сначала вычислить свою колонку, потом прибавить к ней 5, и только тогда увидишь. А если это еще и между листами, то вообще сложнее (для человека). Гораздо проще запоминать «F5», и искать его на другом листе…
                                  Ну или это дело привычки, но я так и не освоил. Дико бесит когда 1С (даже самые свежие) при экспорте в Excel обязательно включает R1C1, это применяется и другим файлам, и тебе приходится это выключать руками, причем через меню.
                                  • +1
                                    А вы нажмите F2 или Trace Precedents — все влияющие ячейки, аргументы функций подсвечиваются.
                                    Это помогает всегда.
                                    image
                                    Зато RC[5] — это всегда правее на 5 колонок, это не в голове считать какая там пятая буква от начальной.
                                    Да и в режиме вставки (регулируется клавишей F2) можно смело шагать курсором к ячейке, без оглядки на какие бы то ни было координаты.
                                    ;-)
                                    • +1
                                      дело привычек :)
                                      Может вы знаете как переключалку R1C1 вывести хотя бы в в QAT?

                                      Написать макрос из одной строчки и вывести его я знаю, но у меня есть аллегрия на файл personal.xls (плохо себя ведет когда нужно несколько Excel-ей открыть)
                                      • +1
                                        Эх… Я именно однострочным макросом:
                                        image.prntscr.com/image/wN_a7GpTRBaWSroM5PvYng.png
                                        Но когда я открываю несколько экселей — следующие получают на общий personal в режиме reaonly, потому остальные экземпляры видят и кнопки, и макросы.
                            • +1
                              " Если бы у Microsoft был какой-то скриптовый язык, мы бы с радостью его поддержали. "

                              Начиная с Office 2013 / Office 365 у Microsoft есть вполне скриптовый язык, и он даже вполне рабочий. Зовется «Office API».
                              Официально на нем в первую очередь надо писать «приложения» для Store Add-In ов для Офиса, но в принципе можно и просто писать.
                              Они ушли от VBA специально чтобы офис в облаке мог работать с «макросами».
                              Посмотрите, довольно интересный

                              Excel.run(function (ctx) {
                              var activeWorksheet = ctx.workbook.worksheets.getActiveWorksheet();
                              activeWorksheet.getRange("A1:C3").values = 7;
                              return ctx.sync().then(function () {
                              console.log("Success! Set single value in range A1:C3.");
                              });
                              }).catch(function(error) {
                              console.log("Error: " + error);
                              });
                              • +1
                                Проблемы с обновлением серверной части в rpm все так же существует? Мы до сих пор не обновились до последней версии (не считая эту) так как обновление убивает OnlyOffice, тех поддержка ничего внятного не решила, предложив обновляться вручную а это простой сервиса на несколько часов и только силами вашей тех поддержки.
                                • +1

                                  Проблем с обновлением серверной части в rpm нет.
                                  Обновление производится средствами пакетного менеджера или нашим установочным скриптом с пробросом параметра update
                                  После обновления компонентов нужно запустить скрипт communityserver-configure.sh

                                • +1

                                  Вы странные люди. На linux.org.ru, opennet.ru и здесь, на Хабре, просили присылать вам неправильно открывающиеся у вас документы на support@onlyoffice.com. Присылал docx — в ответ была отписка, что поддержка некоммерческой версии не осуществляется, присылал odt — что это не основной формат. И так раза 3 минимум, после чего забил что-либо присылать. На files@ есть смысл присылать..?

                                  • +1
                                    Мы смотрим все присланные файлы. Смысл присылать однозначно есть. Спасибо.
                                    • +1
                                      Здравствуйте! Почта files@ создана специально для проблемных пользовательских файлов, они напрямую попадают к программистам, которые работают с форматами. Присылайте нам, пожалуйста, файлы именно туда. Это очень помогает нам совершенствовать совместимость.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое