Пишем клиент для Голос в связке с CMS Joomla. Часть 2


И снова здравствуйте. На связи @captain. Продолжаем пошаговое написание web-клиента голоса. Это вторая часть( Часть 1 ) и в ней мы разберемся как сверстать основной экран клиента и как вывести карточки постов на него.

Я вижу для себя это вот таким образом:

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

Для Bootstrap наша разметка будет выглядеть вот так:


        <nav>

        </nav>

        <?php require_once(JPATH_ROOT . '/components/com_q/views/item.php'); ?>

Код на pastebin

Внимательный читатель обратил внимание, что в правый div у нас подгружается файл item.php, который отвечает за вывод самого поста. Его мы будем разбирать в следующей части.
Сейчас же я лишь отмечу, что используется элемент navbar за счет чего прокрутка области с карточками постов ограничивается пределами экрана. Таким образом мы прокручиваем только ленту постов, открытый пост прокручивается отдельно.

Я неспроста выбрал такой механизм. Используя его, очень просто знакомиться с лентой постов. Достаточно нажать на пост и и он отображается справа, его значительную часть можно увидеть сразу и решить заслуживает ли он внимания или нет. И так же просто можно перейти к следующему посту, так как ничего "закрывать" не надо. Минимизируем и клики мышки и ожидание.

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

function getDiscussions()
{
     var params = 
     {
         'limit': 100,
         'truncate_body': 240
     }
     golos.api.getDiscussionsByCreated(params, function(err, data){
        if(data.length > 0)
        {           
            data.forEach(function (operation){
                AddBlockX(operation);
            });
        }
     });
}

Код на pastebin

Здесь мы получаем последние 100 постов и 240 символов текста каждого из них. Больше 100 постов за раз API нам не отдаст, да нам и не надо больше. Так как у нас будет несколько различных вариантов заполнения ленты, то логично вывести функцию упаковки и вывода карточки поста в отдельную функцию AddBlockX.

Чуть ниже мы рассмотрим ее подробно. Пока скажу, что нам понадобятся некоторые вспомогательные вещи, например библиотека moment.js, которая классно умеет форматировать даты. Мы используем ее так:

function getCommentDate(adate)
{
    var date = new Date(adate);
    var offset = date.getTimezoneOffset();
    date.setMinutes(date.getMinutes() - offset); 
    return moment(date, "YYYYMMDD").fromNow();
}

Код на pastebin

В результате вместо вывода обычной даты вернется "час назад" или "20 минут назад". Тут же учитывается, что время нод голоса отличается от локального времени пользователя, например от московского времени date.getTimezoneOffset().

Еще один момент. Нам придется работать с тэгами. При портировании стима, почему-то решили, что в эпоху многобайтовых кодировок, лучше использовать транслитерацию для кириллицы. Как по мне, то странное решение, но имеем то что имеем. Поэтому из исходников клиента tolstoy мы стащим вот такой кусок:

var d = /\s+/g,
    rus = "щ    ш  ч  ц  й  ё  э  ю  я  х  ж  а б в г д е з и к л м н о п р с т у ф ъ  ы ь".split(d),
    eng = "shch sh ch cz ij yo ye yu ya kh zh a b v g d e z i k l m n o p r s t u f xx y x".split(d);

function detransliterate(str, reverse) {
    if (!reverse && str.substring(0, 4) !== 'ru--') return str
    if (!reverse) str = str.substring(4)

    // TODO rework this
    // (didnt placed this earlier because something is breaking and i am too lazy to figure it out ;( )
    if(!reverse) {
    //    str = str.replace(/j/g, 'ь')
    //    str = str.replace(/w/g, 'ъ')
        str = str.replace(/yie/g, 'ые')
    }
    else {
    //    str = str.replace(/ь/g, 'j')
    //    str = str.replace(/ъ/g, 'w')
        str = str.replace(/ые/g, 'yie')
    }

    var i,
        s = /[^[\]]+(?=])/g, orig = str.match(s),
        t = /<(.|\n)*?>/g, tags = str.match(t);

    if(reverse) {
        for(i = 0; i < rus.length; ++i) {
            str 
            str 
        }
    }
    else {
        for(i  i < rus.length; ++i) {
            str 
            str 
        }
    }

    if(orig) {
        var restoreOrig 

        for (i  i < restoreOrig.length; ++i)
            str  orig[i]);
    }

    if(tags) {
        var restoreTags 

        for (i  i < restoreTags.length; ++i)
            str  tags[i]);

        str  '').replace(/\]/g, '');
    }

    return str;
}

Код на pastebin

В принципе понятно, что он задает правила прямой и обратной транслитерации. Заострять внимание на нем не будем.

А вот и функция, которая формирует карточку поста. Разберем ее подробнее ниже.

function AddBlockX(operation)
{
    var listWrapper 
    var main_div 
    main_div.classList.add("col-xs-12","q_wrapper");
    var metadata 
    if(metadata.image)
    {
        var image 
    }
    else
    {
        var image 
    }
    var img_div 
    img_div.classList.add("img_div");
    main_div.appendChild(img_div);
    img_div.style.backgroundImage 

    var q_div 
    q_div.classList.add("q_div");
    main_div.appendChild(q_div);

    var title 
    var author 
    var created 
    var last_update 
    var total_payout_value 
    var pending_payout_value 
    var total_pending_payout_value 
    var votes 
    var vl 
    if(total_payout_value > total_pending_payout_value)
    {
        vl = total_payout_value;
    }

    var tags = '';
    if(typeof metadata.tags !== "undefined")
    {
        var tags_count = metadata.tags.length;

        for(var i = 0;i < tags_count;i++)
        {
            if(tags_count > 1)
            {
                tags = tags + " <span class=' -warning'>"+detransliterate(metadata.tags[i], 0)+'</span>';
            }
        }
    }   

    var dt = getCommentDate(created);
    var s = 'onClick="getContentX(\''+operation.permlink.trim() +'\', \''+operation.author.trim() +'\');"';
    q_div.innerHTML = '<h3>'+ title + '</h3>' +  dt +' - Автор: @' + author + '' + '<br/> Голосов <strong>' + votes + '</strong> на сумму <strong>' + vl + '</strong><br/>' + tags;

    var clearFix = document.createElement("div");
    clearFix.classList.add("clearFix");
    main_div.appendChild(clearFix); 
    listWrapper.appendChild(main_div);
}

Код на pastebin

Пройдемся по функции чуть подробнее. Сначала мы создаем div в котором будет все располагаться. Потом получаем метаданные var metadata = JSON.parse(operation.json_metadata); Тут содержатся данные о ссылках и картинках поста, клиенте с которого он был опубликован и т.п. Нас интересует картинка, которую можно было бы вывести в карточке поста. Если она есть if(metadata.image), то выведем ее, иначе выводим заглушку. Картинку выводим как бэкграунд img_div.style.backgroundImage = "url('"+image+"')"; Это позволит выводить анимированные gif, а также центрирует и масштабирует картинку (это мы задаем в css).

Дальше вытаскиваем различные данные поста такие как заголовок, автор, дата создания, выплаты и т.п. Тут же можно определить количество голосов за пост var votes = operation.active_votes.length;.

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

if(typeof metadata.tags !== "undefined")
    {
        var tags_count = metadata.tags.length;
        for(var i = 0;i < tags_count;i++)
        {
            if(tags_count > 1)
            {
                tags = tags + " <span class=' -warning'>"+detransliterate(metadata.tags[i], 0)+'</span>';
            }
        }
    }   

Тут нужно отметить еще такой любопытный момент, что клиент golos.io выдает ссылки в своем формате и нам нужно этого формата придерживаться для совместимости с другими клиентами. Поэтому ссылка на вывод всех постов по тэгу tag будет иметь вид: /created/tag/, а для тэга хобот будет /created/ru--khobot/.

У нас все готово. Объединяем все наши данные для вывода в карточку:

var s = 'onClick="getContentX(\''+operation.permlink.trim() +'\', \''+operation.author.trim() +'\');"';
q_div.innerHTML = '<h3>'+ title + '</h3>' +  dt +' - Автор: @' + author + '' + '<br/> Голосов <strong>' + votes + '</strong> на сумму <strong>' + vl + '</strong><br/>' + tags;

Имя автора так же делаем ссылкой, по которой открывается страница автора со всеми его постами. А заголовок статьи, ссылка, которая вызывает функцию загрузки контента getContentX в соседний блок для просмотра.

Если мы хотим выводить посты определенного автора, то нам поможет вот такая функция:

function getDiscussionsByAuthor(author)
{
    document.getElementById('loader').style = 'display:block';
     var params = 
     {
         'limit': 100,
         'truncate_body': 240,
         'select_authors': [author]
     }
     golos.api.getDiscussionsByCreated(params, function(err, data){
        if(data.length > 0)
        {           
            data.forEach(function (operation){
                AddBlockX(operation);
            });
        }
     });
}

Код на pastebin

а если посты по определенному тэгу, то вот такая:

function getDiscussionsByTags(tags)
{
     var params = 
     {
         'limit': 50,
         'select_tags': tags,
         'truncate_body': 240
     }
     golos.api.getDiscussionsByCreated(params, function(err, data){
        data.sort(compareDate);
        if(data.length > 0)
        {           
            data.forEach(function (operation){
                AddBlockX(operation);
            });
        }
     });
}

Код на pastebin

У нас здесь используется функция для сортировки массива по времени, она очень простая:

function compareDate(a, b)
{
    if(a.created > b.created)
    {
        return -1;
    }
    else{
        return 1;
    }
}

Код на pastebin

Нужна она потому, что мы можем указать несколько тэгов через запятую. А вот посты по ним вернутся к нам вразнобой. И нам самим приходится их упорядочивать.

Это практически все, что мы будем использовать для вывода ленты постов. Будет еще вывод ленты пользователя, но о нем поговорим, когда доберемся до страницы пользователя.

У нас осталось только одно затруднение - ограничение на 100 возвращаемых постов. После прокрутки ленты хорошо бы сделать кнопочку "Дальше" и подгрузить следующие 100 постов. Так в чем же проблема? За дело.

В этом нам помогут параметры start_author и start_permlink для методов getDiscussions*.
Перепишем код следующим образом:

var startAuthor;
var startPermlink;

function getDiscussions(start_author, start_permlink)
{
     start_author = typeof start_author !== 'undefined' ?  start_author : '';
     start_permlink = typeof start_permlink !== 'undefined' ?  start_permlink : '';
     if(start_permlink && start_author)
     {
         var params = 
         {
             'limit': 100,
             'truncate_body': 240,
             'start_author': start_author,
             'start_permlink': start_permlink
         }     
     }
     else
     {
         var params = 
         {
             'limit': 100,
             'truncate_body': 240
         }
     }

     golos.api.getDiscussionsByCreated(params, function(err, data){
        if(data.length > 0)
        {           
            data.forEach(function (operation){
                AddBlockX(operation);
            });
        }
     });
}

function AddBlockX(operation)
{
    ...
    startAuthor = operation.author;
    startPermlink = operation.permlink;
    ...

}

Код на pastebin

А в конец ленты постов добавим кнопку

<button   startPermlink);'>Дальше</button>

Вот теперь у нас получилась полноценная навигация. Думаю что остальные методы из группы getDiscussions* вы адаптируете и без моей помощи.

Пока что все. В следующей статье цикла будем показывать пост, комментарии и все что к этому относится.


И немного пиара. @captain это делегат голоса. Вот его делегатский пост /@captain/post-delegata-captain/
@captain много чего делает для голоса и много чего еще сделает. Поэтому не поленитесь и проголосуйте за него тут /~witnesses или тут https://goldvoice.club/witnesses/


Comments 1