11ty 블로그에 Webmentions 구현
Note: I used Max Böck's article and his code to implement them on my blog.
Webmentions는 다음과 같이 표시됩니다.

1단계. webmention.io 가입
Aaron Parecki은 webmention.io이라는 환상적인 무료 도구를 만들었습니다. 웹 멘션을 받는 호스팅 솔루션입니다.
가입은 indie-auth을 사용하므로 다음과 같이 웹사이트에 링크가 있어야 합니다.
<a href="https://twitter.com/DailyDevTips1" rel="me">Twitter</a>
Twitter 프로필에 웹사이트 도메인이 포함되어 있는지 확인하세요.

2단계. Webmention 컬렉션 링크 추가
일단 로그인하면 도메인에 두 개의 링크를 추가해야 합니다.
<link rel="webmention" href="https://webmention.io/{username}/webmention" />
<link rel="pingback" href="https://webmention.io/{username}/xmlrpc" />
{username}
를 daily-dev-tips.com
와 같은 실제 도메인으로 바꿉니다.3단계. 트윗을 Webmentions로 연결
이제 우리는 웹멘션을 받을 수 있지만 솔직히 누가 우리에게 웹멘션을 보낼까요?
URL에 대해 트윗하는 사람들을 Webmentions로 변환합시다!
이를 위해 bridgy과 같은 호스팅 서비스를 사용할 수 있습니다.
Twitter 아이콘을 클릭하여 로그인하십시오.
그런 다음 웹사이트를 크롤링하고 Twitter에 투표할 수 있습니다.

Bridgy only gets the most recent Tweets, but you can add a Tweet URL in the Resend for the post button.
실제 Webmentions가 포함된 응답은 다음과 같습니다.

4단계. 모든 웹멘션을 검색하는 Eleventy 기능
이제 모든 설정이 완료되었으므로 Eleventy에서 webmention.io API에 대한 모든 Webmentions를 수집하는 함수를 생성할 수 있습니다.
Eleventy에서는
_data
폴더에 사용자 지정 데이터 파일을 추가할 수 있습니다.부르자
webmentions.js
const fs = require('fs');
const fetch = require('node-fetch');
const unionBy = require('lodash/unionBy');
const domain = 'daily-dev-tips.com';
// Load .env variables with dotenv
require('dotenv').config();
// Define Cache Location and API Endpoint
const CACHE_DIR = '_cache';
const API = 'https://webmention.io/api';
const TOKEN = process.env.WEBMENTION_IO_TOKEN;
async function fetchWebmentions(since, perPage = 10000) {
if (!domain) {
// If we dont have a domain name, abort
console.warn('>>> unable to fetch webmentions: no domain name specified in site.json');
return false;
}
if (!TOKEN) {
// If we dont have a domain access token, abort
console.warn('>>> unable to fetch webmentions: no access token specified in environment.');
return false;
}
let url = `${API}/mentions.jf2?domain=${domain}&token=${TOKEN}&per-page=${perPage}`;
if (since) url += `&since=${since}`;
const response = await fetch(url);
if (response.ok) {
const feed = await response.json();
console.log(`>>> ${feed.children.length} new webmentions fetched from ${API}`);
return feed;
}
return null;
}
// Merge fresh webmentions with cached entries, unique per id
function mergeWebmentions(a, b) {
return unionBy(a.children, b.children, 'wm-id');
}
// save combined webmentions in cache file
function writeToCache(data) {
const filePath = `${CACHE_DIR}/webmentions.json`;
const fileContent = JSON.stringify(data, null, 2);
// create cache folder if it doesnt exist already
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR);
}
// write data to cache json file
fs.writeFile(filePath, fileContent, err => {
if (err) throw err;
console.log(`>>> webmentions cached to ${filePath}`);
})
}
// get cache contents from json file
function readFromCache() {
const filePath = `${CACHE_DIR}/webmentions.json`;
if (fs.existsSync(filePath)) {
const cacheFile = fs.readFileSync(filePath);
const cachedWebmentions = JSON.parse(cacheFile);
// merge cache with wms for legacy domain
return {
lastFetched: cachedWebmentions.lastFetched,
children: cachedWebmentions.children
};
}
// no cache found.
return {
lastFetched: null,
children: {}
};
}
module.exports = async function () {
const cache = readFromCache();
if (cache.children.length) {
console.log(`>>> ${cache.children.length} webmentions loaded from cache`);
}
// Only fetch new mentions in production
if (process.env.NODE_ENV === 'production') {
const feed = await fetchWebmentions(cache.lastFetched);
if (feed) {
const webmentions = {
lastFetched: new Date().toISOString(),
children: mergeWebmentions(cache, feed)
}
writeToCache(webmentions);
return webmentions;
}
}
return cache;
}
대용량 파일이지만 기본적으로 다음 위치에서 엔드포인트에 대한 웹 언급을 읽습니다.
https://webmention.io/api/mentions.jf2?domain=${domain}&token=${TOKEN}
그런 다음 캐시 파일과 병합합니다.
이 기능은 일레븐티 블로그를 구축하면 실행되므로 실시간이 아닙니다.
To make it realtime, we can leverage other endpoints, but I won't go into that. Find more on Shawn's blog
이 데이터 파일을 만들면
{{ webmentions }}
라는 변수에 액세스할 수 있습니다.5단계. 블로그에 Webmentions 표시
언급했듯이 이제
{{ webmentions }}
변수가 있습니다.제 경우에는 Webmentions에서 다음 요소를 분리하고 싶습니다.
따라서 블로그 페이지 레이아웃에 다음을 추가해 보겠습니다.
// layouts/post.njk
{% include "partials/components/webmentions.njk" %}
이 Webmentions 파일에서 모든 멘션을 로드합니다.
먼저 현재 페이지의 전체 URL을 가져와야 합니다.
{% set currentUrl %}{{ site.url + page.url | uniUrlFilter }}{% endset %}
내 URL에 꽤 많은 이모티콘을 사용하고 있기 때문에 uniUrlFilter를 만들었습니다.
module.exports = function uniUrlFilter(value) {
return encodeURI(value);
};
그런 다음 이 특정 URL에 대한 Webmentions를 검색해야 합니다.
{%- set mentions = webmentions.children | getWebmentionsForUrl(currentUrl) -%}
그리고 이 필터는 그것들을 깔끔한 배열로 정렬할 것입니다.
const sanitizeHTML = require('sanitize-html');
module.exports = function getWebmentionsForUrl(webmentions, url) {
const likes = ['like-of'];
const retweet = ['repost-of'];
const messages = ['mention-of', 'in-reply-to'];
const hasRequiredFields = entry => {
const { author, published, content } = entry;
return author.name && published && content;
};
const sanitize = entry => {
const { content } = entry;
if (content['content-type'] === 'text/html') {
content.value = sanitizeHTML(content.value);
}
return entry;
};
return {
'likes': webmentions
.filter(entry => entry['wm-target'] === url)
.filter(entry => likes.includes(entry['wm-property'])),
'retweet': webmentions
.filter(entry => entry['wm-target'] === url)
.filter(entry => retweet.includes(entry['wm-property']))
.filter(hasRequiredFields)
.map(sanitize),
'messages': webmentions
.filter(entry => entry['wm-target'] === url)
.filter(entry => messages.includes(entry['wm-property']))
.filter(hasRequiredFields)
.map(sanitize)
};
}
보시다시피 Webmention의 세 가지 다른 요소를 필터링하여 조각별로 정렬합니다.
그런 다음
webmentions.njk
부분에서 루프를 반복할 수 있습니다.<ol>
{% for webmention in mentions.likes %}
<li class="webmentions__item">
<a {% if webmention.url %}href="{{ webmention.url }}"{% endif %} target="_blank" rel="noopener noreferrer" title="{{ webmention.author.name }}">
{% if webmention.author.photo %}
<img src="{{ webmention.author.photo }}" alt="{{ webmention.author.name }}" width="48" height="48" loading="lazy">
{% else %}
<img src="{{ '/assets/images/avatar-default.jpg' | url }}" alt="" width="48" height="48">
{% endif %}
</a>
</li>
{% endfor %}
</ol>
<ol>
{% for webmention in mentions.retweets %}
<li class="webmentions__item">
<a {% if webmention.url %}href="{{ webmention.url }}"{% endif %} target="_blank" rel="noopener noreferrer" title="{{ webmention.author.name }}">
{% if webmention.author.photo %}
<img src="{{ webmention.author.photo }}" alt="{{ webmention.author.name }}" width="48" height="48" loading="lazy">
{% else %}
<img src="{{ '/assets/images/avatar-default.jpg' | url }}" alt="" width="48" height="48">
{% endif %}
</a>
</li>
{% endfor %}
</ol>
<ol>
{% for webmention in mentions.messages %}
<li class="webmentions__item">
<a {% if webmention.url %}href="{{ webmention.url }}"{% endif %} target="_blank" rel="noopener noreferrer" title="{{ webmention.author.name }}">
{% if webmention.author.photo %}
<img src="{{ webmention.author.photo }}" alt="{{ webmention.author.name }}" width="48" height="48" loading="lazy">
{% else %}
<img src="{{ '/assets/images/avatar-default.jpg' | url }}" alt="" width="48" height="48">
{% endif %}
</a>
<strong>{{ webmention.author.name }}</strong>
<time class="dt-published" datetime="{{ webmention.published | w3DateFilter }}">
{{ webmention.published | dateFilter }}
</time>
{{ webmention.content.html | safe }}
</li>
{% endfor %}
</ol>
이제 몇 가지 스타일을 추가하고 Eleventy 블로그에 Webmentions를 선보일 준비가 되었습니다.
읽어주셔서 감사합니다. 연결합시다!
제 블로그를 읽어주셔서 감사합니다. 내 이메일 뉴스레터를 구독하고 Facebook에 연결하거나
Reference
이 문제에 관하여(11ty 블로그에 Webmentions 구현), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/dailydevtips1/implementing-webmentions-on-a-11ty-blog-5bhg텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)