Уважаемые пользователи Голос!
Сайт доступен в режиме «чтение» до сентября 2020 года. Операции с токенами Golos, Cyber можно проводить, используя альтернативные клиенты или через эксплорер Cyberway. Подробности здесь: https://golos.io/@goloscore/operacii-s-tokenami-golos-cyber-1594822432061
С уважением, команда “Голос”
GOLOS
RU
EN
UA
vp-webdev
6 лет назад

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

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

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

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

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

<div class="row">
    <div class="col-lg-5">
        <nav class="">
            <div class="collapse navbar-collapse" id="items_collapsable_wrapper">
                <div id="items_list_wrapper" class="row">
                </div>
            </div>
        </nav>
    </div>
    <div class="col-lg-7">
        <?php require_once(JPATH_ROOT . '/components/com_q/views/item.php'); ?>
    </div>
</div>

Код на 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.split(rus[i]).join(eng[i]);
            str = str.split(rus[i].toUpperCase()).join(eng[i].toUpperCase());
        }
    }
    else {
        for(i = 0; i < rus.length; ++i) {
            str = str.split(eng[i]).join(rus[i]);
            str = str.split(eng[i].toUpperCase()).join(rus[i].toUpperCase());
        }
    }

    if(orig) {
        var restoreOrig = str.match(s);

        for (i = 0; i < restoreOrig.length; ++i)
            str = str.replace(restoreOrig[i], orig[i]);
    }

    if(tags) {
        var restoreTags = str.match(t);

        for (i = 0; i < restoreTags.length; ++i)
            str = str.replace(restoreTags[i], tags[i]);

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

    return str;
}

Код на pastebin

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

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

function AddBlockX(operation)
{
    var listWrapper = document.getElementById('items_list_wrapper');
    var main_div = document.createElement("div");
    main_div.classList.add("col-xs-12","q_wrapper");
    var metadata = JSON.parse(operation.json_metadata);
    if(metadata.image)
    {
        var image = metadata.image[0];
    }
    else
    {
        var image = '/components/com_q/noimage.png';
    }
    var img_div = document.createElement("div");
    img_div.classList.add("img_div");
    main_div.appendChild(img_div);
    img_div.style.backgroundImage = "url('"+image+"')";

    var q_div = document.createElement("div");
    q_div.classList.add("q_div");
    main_div.appendChild(q_div);

    var title = operation.title;
    var author = operation.author;
    var created = operation.created;
    var last_update = operation.last_update;
    var total_payout_value = operation.total_payout_value;
    var pending_payout_value = operation.pending_payout_value;
    var total_pending_payout_value = operation.total_pending_payout_value;
    var votes = operation.active_votes.length;
    var vl = total_pending_payout_value;
    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='label label-warning'><a href='/created/" + metadata.tags[i] + "'>"+detransliterate(metadata.tags[i], 0)+'</a></span>';
            }
        }
    }   

    var dt = getCommentDate(created);
    var s = 'onClick="getContentX(\''+operation.permlink.trim() +'\', \''+operation.author.trim() +'\');"';
    q_div.innerHTML = '<div class="q_header_wrapper"><h3><a href="javascript:void(0)" '+s+'>'+ title + '</a></h3></div>' +  dt +' - Автор: <a href="/@'+ author +'" title="Все посты пользователя">@' + author + '</a>' + '<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='label label-warning'><a href='/created/" + metadata.tags[i] + "'>"+detransliterate(metadata.tags[i], 0)+'</a></span>';
            }
        }
    }   

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

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

var s = 'onClick="getContentX(\''+operation.permlink.trim() +'\', \''+operation.author.trim() +'\');"';
q_div.innerHTML = '<div class="q_header_wrapper"><h3><a href="javascript:void(0)" '+s+'>'+ title + '</a></h3></div>' +  dt +' - Автор: <a href="/@'+ author +'" title="Все посты пользователя">@' + author + '</a>' + '<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 type='button' onClick='getDiscussions(startAuthor, startPermlink);'>Дальше</button>

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

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


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

0
62.112 GOLOS
На Golos с August 2017
Комментарии (1)
Сортировать по:
Сначала старые