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


Привет! На связи @captain и сегодня у нас по плану третий пост про написание web-клиента GOLOS. И он будет посвящен отображению контента поста и комментариев. Также мы покажем кто проголосовал за пост и с какой силой, когда и кем он был написан, какие его ожидают выплаты и как скоро.

Вот так это будет выглядеть:

Основной метод для получения данных поста это API вызов:
golos.api.getContent с параметрами author и permlink. Но, как обычно все не так просто и нам потребуются дополнительные инструменты.

Начнем с того, что сам по себе пост может быть, как минимум, в 2 форматах. Это markdown и html. И если html наш браузер отображать умеет, то для markdown нужна будет дополнительная библиотека. Это будет marked.js, которая довольно сносно умеет отображать таблицы и блоки кода. Мы получаем данные поста в параметр data и обрабатываем их:

var result = data;
marked.setOptions({
  renderer: new marked.Renderer(),
  gfm: true,
  tables: true,
  breaks: false,
  pedantic: false,
  sanitize: false,
  smartLists: true,
  smartypants: false
});
article.text = result.body;        

а после помещаем в одно из полей объекта article, где уже есть ссылка на автора и permlink. Теперь получаем дату поста:

var date = new Date(result.created);
var offset = date.getTimezoneOffset();
date.setMinutes(date.getMinutes() - offset); 
var dt = date.toLocaleDateString("ru-RU") + ' ' + date.toLocaleTimeString("ru-RU");

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

Еще хотелось бы показать сколько заработал пост и через какое время ожидается выплата в первом окне. За это отвечают следующие поля:

cashout_time - время выплаты денег за текущее окно
mode - какое окно активно - первое или второе
pending_payout_value - сколько будет выплачено в первом окне
total_pending_payout_value - сколько будет выплачено во втором окне

Теперь мы можем сформировать шапку поста:

var header = document.createElement("div");
header.innerHTML = "<h1>"+result.title+
" <br><small>"+dt+" Автор - @"+result.author+" "+ 
follow + "</small></h1>" + '<p>Голосов <strong>'+result.active_votes.length+
'</strong> на сумму <strong>'+vl+'</strong>  выплата '+getCommentDate(result.cashout_time)+'</p>';

Дальше нам нужно показать аватарку пользователя. Она должна быть большая и красивая, чтобы мы видели кого мы читаем. Аватарка содержится в метаданных аккаунта и мы можем получить ее методом golos.api.getAccounts. Так же как и картинку в превью поста, мы выводим аватар в бэкграунд элемента div с закругленными краями. А если метаданные не заполнены или информации об аватарке нет, то выводим картинку с ниндзя, так как человек явно прячется от нас:

var ava = document.createElement("div");
ava.style.float = 'left';
ava.id = 'ava';
document.getElementById('query_header').appendChild(ava);
golos.api.getAccounts([author], function(err, response){
    if(response)
    {
        var ava = document.getElementById("ava");
        if(response[0].json_metadata != 'undefined' && response[0].json_metadata != '{}' && response[0].json_metadata != '')
        {
            var metadata = JSON.parse(response[0].json_metadata);
            if(metadata.profile != 'undefined')
            {
                if(metadata.profile.profile_image != 'undefined')
                {
                    ava.style.backgroundImage = "url('"+metadata.profile.profile_image+"')";

                }else{
                    ava.style.backgroundImage = "url('/ninja.png')";
                }
            }else{
                var ava = document.getElementById("ava");
                ava.style.backgroundImage = "url('/ninja.png')";
            }                   
        }else{
            var ava = document.getElementById("ava");
            ava.style.backgroundImage = "url('/ninja.png')";
        }   
        ava.classList.add('ava_div');
    }
});

Код на pastebin

После этого выводим текст поста. Пропустим его через markdown фильтр и добавим на страницу. Тут все просто:

main_div.innerHTML = marked(article.text);

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

golos.api.getActiveVotes(author, permlink, function(err, data){
    if(data)
        if(data.length > 0)
        {
            var s = '';
            data.forEach(function(operation){
                s = s + "@"+operation.voter+" ";
            });
            document.getElementById('voters').innerHTML = '<hr>Оценили ('+data.length+'): <span >показать</span> <span id="all_votes" style="display: none"><small>' + s + '</small></span>';
        }
    }); 

Код на pastebin

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

Вот практически и все. Остается только показать комментарии. В блокчейне голос цепочка комментариев записывается со ссылкой на родителя, которая хранится в parent-permlink. И у нас есть задача построить из набора таких цепочек дерево комментариев. Для этого мы воспользуемся замечательным приемом - рекурсией. Вот так мы вытащим комментарии первого уровня:

golos.api.getContentReplies(author, permlink, function(err, data){
    if(data.length > 0)
    {
        data.forEach(function(operation){
            var main_div = addComentX(operation);
            document.getElementById('answers_list').appendChild(main_div);
            getRepliesX(operation.author, operation.permlink, main_div)
        });
    }
});

Код на pastebin

а вот так все остальные:

function getRepliesX(author, permlink, parent)
{
    golos.api.getContentReplies(author, permlink, function(err, data){
        if(data.length > 0)
        {
            data.forEach(function(operation){
                var div = addComentX(operation);
                div.classList.add("depth2");
                parent.appendChild(div);
                getRepliesX(operation.author, operation.permlink, div);
            });
        }
    });
}

Код на pastebin

Вывод самого комментария реализует функция:

function addComentX(operation)
{
    // вот этот блок нужен чтобы все красиво вывести со смещением
    var main_div = document.createElement("div");
    main_div.classList.add("panel");
    main_div.classList.add("panel-default");
    var header = document.createElement("div");
    header.classList.add("panel-heading");
    var actions = document.createElement("div");
    actions.style.textAlign = 'right';
    actions.style.marginBottom = '5px';

    //а тут все данные о комментарии - дата, автор, оплата
    var dt = getCommentDate(operation.created);
    var vl = operation.total_pending_payout_value;
    if(operation.total_payout_value > operation.total_pending_payout_value)
    {
        vl = operation.total_payout_value;
    }
    var ava = document.createElement("div");
    ava.style.float = 'left';

    header.innerHTML = "<h3>"+operation.title+" <small>"+dt+" - @"+operation.author+"</small></h3>" + '<p> Голосов '+operation.active_votes.length+' на сумму <strong>'+vl+'</strong></p>'; 
    main_div.appendChild(header);
    header.appendChild(ava);

    var answer = document.createElement("div");
    answer.classList.add("panel-body");
    answer.innerHTML = marked(operation.body);

    main_div.appendChild(answer);
    main_div.appendChild(actions);
    document.getElementById('answer').style = 'display: block';

    golos.api.getAccounts([operation.author], function(err, response){
        if(response)
        {
            if(response[0].json_metadata != 'undefined' && response[0].json_metadata != '{}' && response[0].json_metadata != '')
            {
                var metadata = JSON.parse(response[0].json_metadata);
                if(metadata.profile != 'undefined')
                {
                    if(metadata.profile.profile_image != 'undefined')
                    {
                        //var ava = document.getElementById("ava");
                        ava.style.backgroundImage = "url('"+metadata.profile.profile_image+"')";
                        ava.classList.add('ava_div');
                    }else{
                        ava.style.backgroundImage = "url('/ninja.png')";
                        ava.classList.add('ava_div');
                    }
                }else{
                    ava.style.backgroundImage = "url('/ninja.png')";
                    ava.classList.add('ava_div');
                }                   
            }else{
                ava.style.backgroundImage = "url('/ninja.png')";
                ava.classList.add('ava_div');
            }               
        }
    });
    return main_div;
    }

Код на pastebin

В следующем посте нас ждет добавление постов и комментариев, голосование за посты, комментарии и установка флагов.


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


Comments 1