24 дня индивеба: Комментарии часть два

Если кратко: Пример того, как можно отрендерить веб-меншены, используя Svelte и сервис Webmention.io.

Я уже писал про вебменшены, а сегодня хочу поделиться небольшой штукой, которую написал на Svelte: простая рендерилка веб-меншенов, которые она достаёт с Webmention.io, куда я делегирую свои веб-меншены.

Ответы, лайки и репосты

Webmention.io даёт довольно простой API, где можно получить в виде JSON веб-меншены для переданного URL, и внутри этого JSON есть информация про автора, иногда картинка, и тип события. Я пока обрабатываю только like-of, repost-of и in-reply-to.

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

Мне кажется, что проще всего будет объяснить свой компонент в стиле литературного программирования.

В целом, Svelte-компоненты состоят из скриптов, стилей и разметки. Скрипт заворачивается в <script>, стили — в <style>, ну а разметку и заворачивать не надо.

<script>
let likes = [] let replies = [] let reposts = [] let other = [] let loc = document.location.href; let mentionsRequest = fetch('https://webmention.io/api/mentions.jf2?target=' + loc, { headers: { accept: 'appplication/json' } }) .then(r => r.json()) .then(mention_feed => { if (mention_feed && mention_feed.children) { mention_feed.children.forEach(mention => { switch (mention['wm-property']) { case 'like-of': likes.push(mention); break; case 'repost-of': reposts.push(mention); break; case 'in-reply-to': replies.push(mention); break; default: other.push(mention); break; } }) } likes = likes reposts = reposts replies = replies other = other }) </script>

Я запрашиваю все вебменшены и раскладываю их по четырёх категориям, в зависимости от wm-source: in-reply-to, repost-of, like-of, прочее.

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

В Svelte добавляет в HTML немного деклараций, например {\#await promise}контент{/await} будет отрисовывать контент, пока промис не зарезолвится. Выше в скрипте я сохранил запрос к API в mentionsReq, поэтому могу отрисовать плейсхолдер, пока ждём ответа от API.


{#await mentionsRequest}

<p>Подгружаю вебменшены...</p>

{/await}

Чтобы отрисовать ответы, я прохожусь по массиву replies и рисую каждый ответ. {@html reply.content.html} отрисует «сырой» HTML, который пришёл из API. Так как ответ написал не я, то я завернул это всё в <blockquote>, и в футере ставлю ссылку на оригинал.


{#if replies.length}
    <section>

        {#each replies as reply, i}
            <article class="reply">
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-message-square"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
                {#if reply.content && reply.content.html}
                <blockquote class="reply">
                    {@html reply.content.html}
                    <footer class="source">
                        <a class="author" href="{reply.author.url}">
                            <img class="avatar" src="{reply.author.photo}" alt=""> {reply.author.name}</a>
                        <a class="link" href="{reply['wm-source']}">{reply['published']}</a>
                    </footer>
                </blockquote>
                {:else}
                <a href="{reply['wm-source']}">{reply['wm-source']}</a>
                {/if}
            </article>
        {/each}
    </section>
{:else}
    <p>Пока ответов нет. Если напишешь ответ — пришли <a rel="nofollow" href="https://indieweb.org/Webmention">вебменшен</a>!</p>
{/if}

С лайками и репостами ещё проще — я решил рисовать только аватарку и ссылку на того, кто лайкнул.



{#if likes.length}
    <section>
        <svg aria-label="лайки" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
        <ul class="likes">
        {#each likes as like}
            <li class="like"><a title="{like.author.name}" href="{like.author.url}"><img alt="{like.author.name}" class="avatar" src="{like.author.photo}"></a></li>
        {/each}
        </ul>
    </section>
{/if}

{#if reposts.length}
    <section>
        <svg aria-label="репосты" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-repeat"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>
        <ul class="reposts">
        {#each reposts as repost}
            <li class="repost"><a title="{repost.author.name}" href="{repost.author.url}"><img alt="{repost.author.name}" class="avatar" src="{repost.author.photo}"></a></li>
        {/each}
        </ul>
    </section>
{/if}

И, наконец, корзина «остальное» — просто рисую ссылками.


{#if other.length}
    <section>
        <p>Вебменшены, которые не лайки-репосты и не ответы:</p>
        <ul>
            {#each other as mention}
                <li><a href="{mention['wm-source']}">{mention['wm-source']}</a></li>
            {/each}
        </ul>
    </section>
{/if}

Svelte добавляет немного себя в HTML, но мне кажется, что это всё ещё читается почти как HTML. SVG-иконки взяты из проекта Feather Icons.

Компонент я скомпилировал в ES6-модуль, а использование выглядит примерно так:

import Webmentions from '/webmentions.mjs';

new Webmentions({
	target: document.querySelector('#webmentions')
})

В принципе, из этого можно сделать рендеринг на стороне сервера, но в таком, не-server-side-виде люди увидят самые свежие упоминания на момент загрузки страницы, а не упоминания-на-момент-сборки-сайта: такое решение подойдёт даже статическим сайтам. Но получается, что вебменшены видны только тем людям, у которых включён джаваскрипт (и в случае, если я ничего не сломал).

В целом, я довольно много за эти дни написал о том, как публиковать, но есть и обратная сторона — как читать. У сообщества есть несколько наработок на эту тему, про которые расскажу в ближайшие дни (спойлеры: RSS/Atom, JSONFeed, WebSub/PuSH, h-feeds, Microsub).


Момент, который стоит учитывать, чтобы получилось отправить свой вебменшен: у записи-ответа должен быть свой URL. Вообще у всех записей должен быть свой URL. Это необязательно должна быть отдельная страница, это может быть ссылка-фрагмент (вида example.com/#some-identifier), главное, чтобы по этой ссылке можно было достать обратно h-entry. Проверить, что пост размечен понятным для машин способом можно на IndieWebify.me.

И теперь, когда мой сайт показывает веб-меншены, можно отправить свой — и, если всё ок, через минуту-две увидеть его здесь.

День 8: логин · День 10: ридер

Тим опубликовал

Если понравился этот пост, то подпишись на мой Патреон