Przewiń do treści 

Ale kanał…

Opublikowany:
Autor:
Comandeer
Projekt:
Blog
Kategorie:
Adwent 2025
JavaScript

Dzisiaj nadeszła pora na kolejny element bloga, który już od dłuższego czasu czekał na poprawki: kanały Atom.

Kanały Atom

Dawno, dawno temu ktoś wpadł na szalony pomysł: a co jeśliby pozwolić ludziom czytać treść ze stron internetowych jak i kiedy chcą? Zamiast musieć wchodzić na konkretną stronę WWW, ktoś mógłby po prostu dostać informację o nowym wpisie na blogu czy zdjęciu w galerii podobnie, jak dostaje maila. Ot, taki newsletter, tylko że nie przez maila. Tak powstały tzw. feeds (kanały). W największym skrócie: kanał to plik w formacie innym niż HTML, w którym znajduje się treść strony i który jest przystosowany do czytania przez tzw. feed readers (czytniki kanałów). Te czytniki pobierają kanały ze stron co pewien czas, by sprawdzić, czy na stronie pojawiło się coś nowego. Osobiście używam ich jako głównego sposobu na dowiadywanie się o nowościach ze światka webdevu.

Obecnie mamy trzy standardy kanałów: Really Simple Syndication (RSS), Atom oraz JSON Feed. Te dwa pierwsze to formaty oparte o XML, podczas gdy ostatni, jak sama nazwa sugeruje, o JSON. Na tym blogu stosuję format Atom.

Problemy z kanałem

Przy okazji odświeżenia bloga dodałem też do nagłówków w postach linki odsyłające do konkretnych sekcji:

HTML
<h2 id="odświeżone-bloczki"> <!-- 2 -->
	<a class="header-anchor" href="#odświeżone-bloczki">Odświeżone bloczki</a> <!-- 1 -->
</h2>

Zawartość nagłówka jest otoczona linkiem (1), który odsyła do tego nagłówka przy pomocy kotwicy (2). Problem polega na tym, że atrybut [href] zawiera samą nazwę kotwicy, nie cały URL. A to sprawia, że gdy taki link zostanie wyciągnięty poza oryginalny kontekst (np. do czytnika kanałów), linki w nagłówkach przestają działać. I od dłuższego czasu odkładałem poprawienie tego na później, aż w końcu…

Dygresja

Jeśli zastanawiasz się, czemu wybrałem taki sposób linkowania sekcji, polecam artykuł Amber Wilson o dostępnych kotwicach.

…aż w końcu zmieniłem też bloczki kodu i do problemu z nagłówkami doszedł kolejny:

Bloczek kodu HTML, nad którym znajduje się tekst "HTML Kopiuj".

Fragment wpisu Inkunabuły mądrości w czytniku kanałów

Z racji tego, że czytnik kanałów wycina potencjalnie niebezpieczne elementy, przycisk do kopiowania zawartości bloczków kodu zamienił się w zwykły tekst. Co więcej, nawet gdyby mój czytnik zostawił w tym miejscu przycisk, to i tak by on nie zadziałał, bo nie ma sposobu na dodanie JS-a do kanału Atom. Innymi słowy: kanał mojego bloga miał już dwa niedziałające elementy. Przyszła pora, by coś z tym zrobić!

Naprawa HTML-a

Postanowiłem dodać filtr w Eleventy, który brałby treść posta i wycinał z niego wszystkie elementy, które nie działają w kanale. Problem polega jednak na tym, że w momencie stosowania filtru zawartość posta jest już sparsowana z Markdowna do HTML-a. A to oznacza, że muszę parsować HTML. W tym celu postanowiłem użyć biblioteki cheerio, która dostarcza API podobnego do jQuery. Ostatecznie powstał następujący filtr:

JavaScript
import { load } from 'cheerio'; // 2

const MORE_COMMENT_REGEX = /<!--\s*more\s*-->/; // 10

eleventyConfig.addFilter( 'rss_content', ( post ) => { // 1
	const postURL = cfUrl( new URL( post.data.permalink, post.data.site.url ).href ); // 7
	const $ = load( post.content ); // 3

	$( ':where(h1, h2, h3, h4, h5, h6) a' ).each( ( _, link ) => { // 4
		const $link = $( link );
		const originalHref = $link.attr( 'href' ); // 5

		$link.attr( 'href', postURL + originalHref ); // 6
	} );
	$( '.code__copy' ).remove();

	return $( 'body' ).html().replace( MORE_COMMENT_REGEX, '' ); // 9
} );

Dodajemy nowy filtr o nazwie rss_content (1). Przekazujemy mu obiekt wpisu. Następnie importujemy funkcję load() z biblioteki cheerio (2). Pozwala ona sparsować kod HTML do DOM-u. Używamy jej do sparsowania zawartości postu (3). Na tak stworzonym drzewku wyszukujemy wszystkie linki w naglówkach (4) i dla każdego z nich pobieramy wartość atrybutu [href] (5) oraz zamieniamy go na absolutny URL (6) – a więc #odświeżone-bloczki zamienią się na https://blog.comandeer.pl/inkunabuly-madrosci#odświeżone-bloczki. Absolutny URL uzyskujemy, sklejając link do posta z linkiem do strony przy pomocy konstruktora URL (7). Link do posta jest dostarczony przez Eleventy, natomiast link do strony – przeze mnie przy pomocy mechanizmu globalnych plików z danymi. Po naprawieniu linków, wyszukujemy i usuwamy wszystkie przyciski do kopiowania kodu (8). Na koniec wyciągamy kod HTML z DOM-u i usuwamy z niego komentarz <!--more--> (9) przy pomocy wyrażenia regularnego (10). Mimo usilnych starań nie udało mi się usunąć go w bardziej przyzwoity sposób, używając API biblioteki cheerio. Warto tutaj zwrócić uwagę, że kod HTML wyciągamy z elementu body. Biblioteka cheerio parsuje bowiem HTML zgodnie z regułami opisanymi w specyfikacji, a więc – jako pełny dokument, co sprawia, że dodaje brakujące elementy html i body. Można też wymusić na cheerio parsowanie w trybie fragmentu, podając false jako trzeci argument do funkcji load().

Dygresja

Komentarz <!--more--> to technika, którą ukradłem z… WordPressa. Służy on do oddzielania wstępu artykułu od reszty treści. Wstęp jest widoczny na stronie głównej bloga wraz z linkiem Czytaj więcej, prowadzącym do podstrony z już pełną wersją artykułu.

Tajemnicza funkcja cfUrl() z poprzedniego fragmentu kodu wycina z przekazanego linku rozszerzenie .html lub fragment index.html:

JavaScript
function cfUrl( url ) {
	return url.replace( /index\.html$/, '' ).replace( /\.html$/, '' );
}

Dzięki temu linki typu /blog-2.0.html lub /kategorie/javascript/index.html zmienią się odpowiednio w /blog-2.0 oraz /kategorie/javascript.

Tak stworzony filtr dodajemy następnie do szablonu kanałów, w miejscu, w którym wyświetlana jest treść posta:

Liquid
<content type="html"><![CDATA[{{ post | rss_content }}]]></content>

I to w sumie tyle! W końcu kanały bloga powinny działać poprawnie.

Komentarze

Przejdź do komentarzy bezpośrednio na Githubie.