<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/feeds/stylesheet.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	
	
	<link href="https://blog.comandeer.pl/feeds/feed.xml" rel="self" type="application/atom+xml"/>
	<link href="https://blog.comandeer.pl/" rel="alternate" type="text/html"/>
	<updated>2026-03-06T00:04:45.711Z</updated>
	<id>https://blog.comandeer.pl/feeds/feed.xml</id>
	<title type="html">Comandeerowy blog</title>
	
		<subtitle>Kolejny nudny blog kolejnego nudnego fanatyka JS-a. Nie czytać, nie warto tracić czasu.</subtitle>
	
	
		
	
	
		<entry>
			<title type="html">Piękno CSS-a</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/piekno-cssa" rel="alternate" type="text/html"/>
			<published>2026-03-05T21:57:00.000Z</published>
			<updated>2026-03-05T21:57:00.000Z</updated>
			<id>https://blog.comandeer.pl/piekno-cssa</id>
			
				<summary><![CDATA[Opublikowałem swoją prezentację o CSS-ie z OLX Frontend Masterclass]]></summary>
			
			<content type="html"><![CDATA[<p>3 marca miałem okazję wygłosić prelekcję na wydarzeniu <a href="https://evenea.pl/pl/wydarzenie/masterclassfrontend" rel="noreferrer noopener">OLX Frontend Masterclass</a> w Poznaniu. Opowiadałem tam o nowych ficzerach w CSS-ie. <em>Przepiękne</em> demo z tej prezentacji <a href="https://github.com/Comandeer/cssdemo" rel="noreferrer noopener">wrzuciłem już na GitHuba</a>. Z kolei <a href="https://www.youtube.com/watch?v=Mcqm15d-qmI" rel="noreferrer noopener">zapis streama z wydarzenia</a> jest dostępny na YouTube.</p>
<p>Dzięki za wpadnięcie, żeby posłuchać, i dzięki za ciekawe dyskusje w trakcie networkingu!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Prelekcjowy marzec</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/prelekcjowy-marzec" rel="alternate" type="text/html"/>
			<published>2026-02-20T21:29:00.000Z</published>
			<updated>2026-02-20T21:29:00.000Z</updated>
			<id>https://blog.comandeer.pl/prelekcjowy-marzec</id>
			
				<summary><![CDATA[W marcu 2026 będzie mnie można usłyszeć na aż dwóch wydarzeniach: OLX Masterclass Frontend oraz Warsaw IT Days!]]></summary>
			
			<content type="html"><![CDATA[<p>Marzec zapowiada się niezwykle pracowicie, bo wygłoszę w nim aż dwie prelekcje – i to na dwa zupełnie różne tematy!</p>
<h2 id="olx-masterclass-frontend"><a class="header-anchor" href="https://blog.comandeer.pl/prelekcjowy-marzec#olx-masterclass-frontend">OLX Masterclass Frontend</a></h2>
<p><strong>3 marca 2026 o godzinie 18:00</strong> rozpocznie się <a href="https://evenea.pl/pl/wydarzenie/masterclassfrontend" rel="noreferrer noopener">OLX Masterclass Frontend</a>. Można w nim uczestniczyć zarówno fizycznie, w <a href="https://maps.app.goo.gl/A6K6svPFJfkEbTD67" rel="noreferrer noopener">poznańskim biurze OLX</a>, jak i <a href="https://docs.google.com/forms/d/e/1FAIpQLSeAFoynaDeryxixlrYik4RJScBwKjh9ybqe5-GL6xrxrfXPIQ/viewform" rel="noreferrer noopener">dołączyć online</a>. Wstęp na wydarzenie jest darmowy. Usłyszeć&nbsp;będzie można trzy prelekcje o tematyce webdevowej. Ja opowiadać będę o zmianach, jakie zaszły w nowoczesnym CSS-ie – w tym zagnieżdżaniu, warstwach czy regule <code>@scope</code> – oraz o tym, jak mogą one wpłynąć na to, jak piszemy i zarządzamy stylami. Dla transparentności: za tę prelekcję <strong>pobieram wynagrodzenie</strong>. Po wszystkim będzie można się napić kawy i pogadać.</p>
<h2 id="warsaw-it-days-2026"><a class="header-anchor" href="https://blog.comandeer.pl/prelekcjowy-marzec#warsaw-it-days-2026">Warsaw IT Days 2026</a></h2>
<p>Z kolei <strong>19 marca 2026</strong> (online) i <strong>20 marca 2026</strong> (na <a href="https://maps.app.goo.gl/5UnHbp2A3MkXbmMZ9" rel="noreferrer noopener">PGE Narodowym w Warszawie</a>) odbywa się kolejna edycja <a href="https://warszawskiedniinformatyki.pl/" rel="noreferrer noopener">Warsaw IT Days</a>. Można posłuchać ponad 300 prelekcji na 25 różnych ścieżkach tematycznych.</p>
<p><picture><source type="image/avif" srcset="/assets/images/prelekcyjny-marzec/wdi-440w.avif 440w, /assets/images/prelekcyjny-marzec/wdi-880w.avif 880w, /assets/images/prelekcyjny-marzec/wdi-1024w.avif 1024w" sizes="90vw"><source type="image/webp" srcset="/assets/images/prelekcyjny-marzec/wdi-440w.webp 440w, /assets/images/prelekcyjny-marzec/wdi-880w.webp 880w, /assets/images/prelekcyjny-marzec/wdi-1024w.webp 1024w" sizes="90vw"><source type="image/png" srcset="/assets/images/prelekcyjny-marzec/wdi-440w.png 440w, /assets/images/prelekcyjny-marzec/wdi-880w.png 880w, /assets/images/prelekcyjny-marzec/wdi-1024w.png 1024w" sizes="90vw"><img src="/assets/images/prelekcyjny-marzec/wdi-1024w.png" width="1024" height="1024" style="aspect-ratio: 1024 / 1024" alt="" loading="lazy" decoding="async"></picture></p>
<p>Moje wystąpienie, <cite lang="en">Giving control back to the user</cite>, będzie transmitowane online <strong>19 marca 2026 o 14:35</strong>. Będę mówił o inkluzywnym designie, a dokładniej – o respektowaniu preferencji osób korzystających z naszej strony WWW. <a href="https://warszawskiedniinformatyki.pl/registration.html" rel="noreferrer noopener">Bilety można kupić&nbsp;na stronie wydarzenia</a>.</p>
<p>Obydwa wystąpienia będą w całości po angielsku. Serdecznie zapraszam!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Sadząc drzewa</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/sadzac-drzewa" rel="alternate" type="text/html"/>
			<published>2026-01-31T22:06:00.000Z</published>
			<updated>2026-01-31T22:06:00.000Z</updated>
			<id>https://blog.comandeer.pl/sadzac-drzewa</id>
			
				<summary><![CDATA[Narzędzia pozwalają wypluwać przy pomocy JSX-a różne typy drzewek, nie tylko Reactowe. W tym wpisie przyglądam się, jak to zrobić.]]></summary>
			
			<content type="html"><![CDATA[<p>Dawno, dawno temu pokazywałem, jak przygotować <a href="https://blog.comandeer.pl/prymitywna-implementacja-mitycznej-funkcji-react-createelement">własną wersję funkcji <code>React.createElement()</code></a>. Jednak nic więcej z nią nie zrobiliśmy. A przecież możemy ją wykorzystać w JSX-ie!</p>
<h2 id="jsx"><a class="header-anchor" href="https://blog.comandeer.pl/sadzac-drzewa#jsx">JSX</a></h2>
<p>Wypada jednak na samym początku wspomnieć, czym w ogóle jest JSX. Za <a href="https://facebook.github.io/jsx/" rel="noreferrer noopener">oficjalną&nbsp;dokumentacją</a>:</p>
<blockquote>
<p lang="en">JSX is an XML-like syntax extension to ECMAScript without any defined semantics. […] It's intended to be used by various preprocessors (transpilers) to transform these tokens into standard ECMAScript.</p>
<p>[JSX to rozszerzenie składni ECMAScript o składnię podobną do XML-a bez definiowania konkretnej semantyki. […] Jest przeznaczone do użytku przez różnego rodzaju preprocesory (transpilery) do transformacji tych tokenów w standardową składnię ECMAScriptu.]</p>
</blockquote>
<p>Przekładając to na ludzki – JSX pozwala umieszczać w kodzie JS coś, co <em>wygląda</em> jak kod XML:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSX</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> component</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">article</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Hello, world!&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">p</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Some content&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">p</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">article</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Niemniej kod w takiej postaci nie ma być rozumiany przez przeglądarki czy środowiska uruchomieniowe typu Node.js. Taki kod powinien być najpierw przepuszczony przez jakieś narzędzie (transpiler), które przetłumaczy go na zwyczajny kod JS. Praktycznie każdy szanujący się&nbsp;transpiler JS-a ma wsparcie dla JSX-a (np. <a href="https://www.typescriptlang.org/docs/handbook/jsx.html" rel="noreferrer noopener">TypeScript ma wbudowane</a>, a <a href="https://babeljs.io/docs/babel-plugin-transform-react-jsx" rel="noreferrer noopener">Babel potrzebuje pluginu</a>). Jeśli przepuścimy przez jeden z nich powyższy kod, powinniśmy otrzymać coś takiego:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> component</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> React.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'article'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	React.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'h1'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Hello, world!'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ), </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	React.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'p'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Some content'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Każdy z XML-owych elementów w naszym JSX-ie został zamieniony na wywołanie funkcji <code>React.createElement()</code>. Jest też oddana relacja między poszczególnymi elementami – wywołania dla <code>h1</code> (2) i <code>p</code> (3) znajdują się wewnątrz wywołania dla <code>article</code> (1). Dzięki temu React jest w stanie odtworzyć w swoim vDOM-ie dokładnie takie drzewko, jakie zaprojektowaliśmy w JSX-ie.</p>
<div class="note" role="note" aria-labelledby="note-48"><p class="note__label" id="note-48">Dygresja</p><div class="note__content"><p>Dokładniejszy opis tej funkcji znajduje się w przywołanym już we wstępie <a href="https://blog.comandeer.pl/prymitywna-implementacja-mitycznej-funkcji-react-createelement">artykule o tworzeniu własnej wersji funkcji <code>React.createElement()</code></a>.</p></div></div>
<h2 id="wykorzystanie-własnej-funkcji"><a class="header-anchor" href="https://blog.comandeer.pl/sadzac-drzewa#wykorzystanie-własnej-funkcji">Wykorzystanie własnej funkcji</a></h2>
<p>Domyślnie JSX jest zawsze tłumaczony do wywołań funkcji <code>React.createElement()</code>. A co jeśli nie chcemy korzystać&nbsp;z Reacta? Wówczas mamy dwie możliwości podmiany tej funkcji.</p>
<p>Pierwsza z nich, mniej elegancka, to… stworzenie funkcji <code>React.createElement()</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> React</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">		// Tutaj jakiś kod</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">    }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">};</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Niemniej istnieje też bardziej elegancka metoda: <em>nadpisanie semantyki JSX-a</em>. Czyli powiedzenie naszemu transpilerowi, jak dokładnie ma transformować kod JSX. Minusem tej metody jest różnica w konfiguracji poszczególnych narzędzi. W przypadku jednak tych najpopularniejszych (TypeScript, Babel, <a href="https://esbuild.github.io/api/#jsx-factory" rel="noreferrer noopener">esbuild</a>) można wykorzystać <a href="https://www.johno.com/jsx-pragma" rel="noreferrer noopener">pragmę</a> – czyli komentarz wskazujący odpowiednią funkcję:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSX</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">/* @jsx myFunction */</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> component</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">article</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Hello, world!&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">p</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Some content&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">p</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">article</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>W tym przypadku informujemy transpiler, że ma używać funkcji o nazwie <code>myFunction()</code>. Dzięki temu po transpilacji dostaniemy następujący kod:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">/* @jsx myFunction */</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> component</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> myFunction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'article'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	myFunction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'h1'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Hello, world!'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ),</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	myFunction</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'p'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">null</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Some content'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> )</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">);</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Dzięki temu możemy podstawić naszą&nbsp;implementację <code>React.createElement()</code>, która czekała na ten moment <em>niemalże 9 lat</em>:</p>
<div><embed- src="https://codepen.io/Comandeer/pen/NPrYRaw"><p class="embed-fallback"><a href="https://codepen.io/Comandeer/pen/NPrYRaw" rel="noreferrer noopener">Przejdź bezpośrednio do osadzonej treści na CodePenie.</a></p></embed-></div>
<div class="note" role="note" aria-labelledby="note-49"><p class="note__label" id="note-49">Dygresja</p><div class="note__content"><p><a href="https://blog.codepen.io/documentation/es-modules-on-codepen/#react-and-jsx" rel="noreferrer noopener">CodePen pozwala używać Babela</a> do pisania JS-a, dzięki czemu działa na nim JSX.</p></div></div>
<p>Ale wypluwanie elementów HTML z JSX-a jest <em>nudne</em>. Tak naprawdę można generować absolutnie wszystko, co da się&nbsp;zapisać jako drzewko elementów. Jak np. tekstowe drzewko plików:</p>
<div><embed- src="https://codepen.io/Comandeer/pen/JoKLRyg"><p class="embed-fallback"><a href="https://codepen.io/Comandeer/pen/JoKLRyg" rel="noreferrer noopener">Przejdź bezpośrednio do osadzonej treści na CodePenie.</a></p></embed-></div>
<p>W tym przypadku funkcja obsługująca JSX wygląda następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> jsx</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">tag</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">attributes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">...</span><span style="color:#E36209;--shiki-dark:#FFAB70">children</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#005CC5;--shiki-dark:#79B8FF">name</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> attributes; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> childrenContent</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> children.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">map</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">child</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `${</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> child</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }`</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replaceAll</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#D73A49;--shiki-dark:#F97583">^</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#D73A49;--shiki-dark:#F97583">gm</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">|   '</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replaceAll</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\n\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">join</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `| - ${</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> name</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }${</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> childrenContent</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Z JSX-owego elementu pobieramy atrybut <code>name</code> (1) – to nazwa katalogu lub pliku. Zwracamy ją&nbsp;(2) jako ciąg tekstowy razem z fajką (<code>|</code>), wskazującą aktualny poziom zagłębienia, oraz ciągami tekstowymi zwróconymi przez dzieci. Te nieco modyfikujemy, dodając do każdego pliku/katalogu brakujące fajki (3), a następnie łącząc wszystkie dzieci w jeden duży ciąg tekstowy (4).</p>
<p>Natomiast JSX użyty do tego przykładu wygląda następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSX</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">/* @jsx jsx */</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> component</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">directory</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">file</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"README.md"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">directory</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"src/"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">file</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"index.ts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">directory</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">directory</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><h2 id="komponenty"><a class="header-anchor" href="https://blog.comandeer.pl/sadzac-drzewa#komponenty">Komponenty</a></h2>
<p>Niemniej w JSX-ie oprócz zwyczajnych elementów istnieją również komponenty, a więc elementy, których nazwy zaczynają&nbsp;się wielką literą. Spójrzmy na taki przykład:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JSX</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> component</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &lt;</span><span style="color:#005CC5;--shiki-dark:#79B8FF">Greeting</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> name</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Comandeer"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> /&gt;;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Jeśli przepuścimy ten kod przez transpiler, otrzymamy mniej więcej coś takiego:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> component</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> React.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( Greeting, { name: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'Comandeer'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>W tym przypadku do <code>React.createElement()</code> jako pierwszy argument nie jest przekazywana nazwa elementu, ale referencja do <code>Greeting</code>. Wypada zatem stworzyć taką funkcję:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Greeting</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( { </span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> } ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> &lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Hello, {name}!&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">h1</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Jako parametr przyjmuje ona obiekt z atrybutami (1), z którego wyciągamy atrybut <code>name</code>. Następnie zwracamy element <code>h1</code>, wewnątrz którego wykorzystujemy ten atrybut do stworzenia powitania (2).</p>
<p>Wypada nieco zmodyfikować naszą funkcję do obsługi JSX-a:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> jsx</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">tag</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">attributes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">...</span><span style="color:#E36209;--shiki-dark:#FFAB70">children</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">typeof</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tag </span><span style="color:#D73A49;--shiki-dark:#F97583">===</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'function'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> tag</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( { </span><span style="color:#D73A49;--shiki-dark:#F97583">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">attributes, children } ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> element</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( tag );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( attributes </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;&amp;</span><span style="color:#D73A49;--shiki-dark:#F97583"> typeof</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> attributes </span><span style="color:#D73A49;--shiki-dark:#F97583">===</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'object'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		Object.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">entries</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( attributes ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">forEach</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( ( [ </span><span style="color:#E36209;--shiki-dark:#FFAB70">name</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">value</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ] ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">			element.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">setAttribute</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( name, value );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		} );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	children.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">forEach</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">child</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		element.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">append</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( child ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> element;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Większość logiki została taka sama, pojawił się jedynie dodatkowy warunek na początku. Sprawdza on, czy przekazany <code>tag</code> jest funkcją&nbsp;(1) i jeśli tak, to wywołuje ją, przekazując jako parametr obiekt zawierający atrybuty oraz własność <code>children</code> z dziećmi (2). Robimy to w taki sposób, żeby zachować Reactowy kształt API. Przy okazji, dzięki użyciu <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/append" rel="noreferrer noopener">metody <code>#append()</code></a> (3) przy dodawaniu dzieci zniknęła potrzeba tworzenia węzła tekstowego. Metoda <code>#append()</code> bowiem, w przeciwieństwie do starszej <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild" rel="noreferrer noopener">metody <code>#appendChild()</code></a>, pozwala dodawać tekst bezpośrednio do elementu.</p>
<p>Nasza obsługa komponentów prezentuje się następująco:</p>
<div><embed- src="https://codepen.io/Comandeer/pen/ogLqBPm"><p class="embed-fallback"><a href="https://codepen.io/Comandeer/pen/ogLqBPm" rel="noreferrer noopener">Przejdź bezpośrednio do osadzonej treści na CodePenie.</a></p></embed-></div>
<p>I tak oto udało nam się przygotować własną, prostą obsługę JSX-a! Oczywiście, nie jest ona skończona, brakuje choćby sensownej obsługi <a href="https://react.dev/reference/react/Fragment" rel="noreferrer noopener">fragmentów</a>. Niemniej jest fajnym punktem wyjścia do dalszych eksperymentów.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Podsumowanie 2025</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/podsumowanie-2025" rel="alternate" type="text/html"/>
			<published>2025-12-31T22:15:00.000Z</published>
			<updated>2025-12-31T22:15:00.000Z</updated>
			<id>https://blog.comandeer.pl/podsumowanie-2025</id>
			
				<summary><![CDATA[Krótko o tym, co się wydarzyło w 2025 roku]]></summary>
			
			<content type="html"><![CDATA[<p>Kolejny rok minął… Nie powiem, był męczący i mało produktywny. A potem wpadłem na <em>iście genialny</em> pomysł i stwierdziłem, że zrobię <a href="https://blog.comandeer.pl/kategorie/adwent-2025/">kalendarz adwentowy</a>. I jakimś cudem dowiozłem! Mimo że byłem pewien, że się nie uda. 19 grudnia złapało mnie choróbsko i kalendarz był zagrożony, ale udało mi zebrać się do kupy i opublikować kolejny wpis 20 grudnia (co prawda o 17, zamiast o północy, ale hej – i tak się liczy!). Tym samym norma na blogu została wyrobiona – i to podwójnie! Ale jeśli ktoś się&nbsp;zastanawia, czy w przyszłym roku też będzie kalendarz adwentowy: nie, na pewno nie. Będzie za to <em>wieniec</em> adwentowy, czyli jeden wpis na każdą niedzielę adwentu.</p>
<p>Jeśli chodzi o rzeczy, z których jestem dumny: na tym blogu to zdecydowanie tekst <a href="https://blog.comandeer.pl/nullius-in-verba"><cite lang="la">Nullius in verba</cite></a> oraz <a href="https://blog.comandeer.pl/projekty/czasomierze/">projekt czasomierzy</a>. I tak, zdaję sobie sprawę, że jest ukończony gdzieś w połowie, ale styczeń i luty brzmią jak dobry czas, by go dokończyć. I ruszyć w końcu z kolejnym zapowiedzianym projektem, czyli <a href="https://blog.comandeer.pl/projekty/doco/">Doco</a>! Bo nie będę ukrywał, moje projekty w tym roku leżały odłogiem i nie miałem sił do nich zaglądnąć. Ale kalendarz trochę mnie rozruszał. A bardzo mi się to przyda, bo oprócz projektów jest przecież również WebKrytyk, który ostatnio też trochę się zakurzył. W tym roku pojawiły się na nim raptem 4 teksty, z których najbardziej podoba mi się ten o <a href="https://www.webkrytyk.pl/2025/05/15/wpadki-i-wypadki-20/" rel="noreferrer noopener">odpowiadających sobie doświadczeniach</a>. Nie można też pominąć faktu, że… <a href="https://www.webkrytyk.pl/2025/12/31/100-lat-czyli-sentymentalna-podroz-w-przeszlosc/#15-lat-minelo" rel="noreferrer noopener">WebKrytyk ma już 15 lat</a>. Yay…!</p>
<p>I to w sumie tyle. Szczęśliwego Nowego Roku 🍾!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Nullius in verba</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/nullius-in-verba" rel="alternate" type="text/html"/>
			<published>2025-12-23T23:06:00.000Z</published>
			<updated>2025-12-23T23:06:00.000Z</updated>
			<id>https://blog.comandeer.pl/nullius-in-verba</id>
			
				<summary><![CDATA[A co, gdybyśmy traktowali webdev bardziej jak naukę?]]></summary>
			
			<content type="html"><![CDATA[<p>Osoby nieco dłużej śledzące moją internetową aktywność mogły zauważyć pewną zmianę mojego podejścia do tematów webdevowych. W mojej dawnej webkrytykowej twórczości, ale i w początkach istnienia tego bloga, o wiele więcej było, hm, <em>emocjonalnych</em> wstawek. Niczym dekadencki poeta z bujną i rozwichrzoną czupryną zdecydowanie stawiałem formę wyżej od treści. Obecnie moje podejście jest zdecydowanie bardziej <em>naukowe</em>. I to bynajmniej nie z powodu nagłego braku bujnej czupryny!</p>
<h2 id="podejście-naukowe"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#podejście-naukowe">Podejście naukowe</a></h2>
<p>Co to jednak znaczy, że traktuję webdev naukowo? Tak naprawdę wyróżniłbym tutaj trzy główne elementy:</p>
<ol>
<li>formę treści,</li>
<li>obecność źródeł,</li>
<li>empiryczność.</li>
</ol>
<h3 id="forma-treści"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#forma-treści">Forma treści</a></h3>
<p>Artykuły naukowe mają ściśle określoną strukturę. Bardzo często opisuje się ją <a href="https://en.wikipedia.org/wiki/IMRAD" rel="noreferrer noopener">akronimem IMRaD</a>:</p>
<ol>
<li><b lang="en">Introduction</b> (wprowadzenie) – wyjaśnienie celu badania, postawienie hipotezy,</li>
<li><b lang="en">Methods</b> (metody badawcze) – w jaki sposób badanie zostało wykonane, kto w nim uczestniczył,</li>
<li><b lang="en">Results</b> (wyniki) – omówienie wyników badania, sprawdzenie, czy wyniki zgadzają się&nbsp;z postawioną hipotezą,</li>
<li><b lang="en">Discussion</b> (polemika) – próba interpretacji wyników, porównanie ich z innymi badaniami z tego zakresu, zaproponowanie możliwych przyszłych kierunków badań.</li>
</ol>
<p>Tak tworzone artykuły powinny zachowywać jak największą obiektywność, skupiając się przede wszystkim na opisie faktów, z pominięciem opinii osób autorskich.</p>
<p>Ja nie jestem naukowcem i siłą mojego bloga jest jednak to, że umieszczam na nim materiały o webdevie okraszone <em>moją</em> opinią. Więc bardziej nazwałbym swoją radosną twórczość <dfn id="esej-hipertekstowy">esejami hipertekstowymi</dfn>. To nic innego jak <a href="https://pl.wikipedia.org/wiki/Esej" rel="noreferrer noopener">eseje</a>, które są opublikowane w formie hipertekstu. Czyli: mogą linkować do innych esejów lub bezpośrednio do źródeł, Równocześnie jednak – raz luźniej, raz ściślej – trzymam się struktury IMRaD. Weźmy taki <a href="https://blog.comandeer.pl/pudelko-z-ciasteczkami">artykuł o Cookie Store API</a>. Na samym początku jest wprowadzenie, w którym zaznaczam problem (<q>relacja Sieci z ciasteczkami od zawsze była skomplikowana</q>). Następnie następuje omówienie wyników badania (w tym wypadku: organoleptycznego sprawdzenia, jak się obsługuje ciasteczka w przeglądarkach). W przypadku artykułów z eksperymentów (jak np. <a href="https://blog.comandeer.pl/jednoplikowe-komponenty">jednoplikowych komponentów</a> czy <a href="https://blog.comandeer.pl/ascss">ASCSS-a</a>) pojawiają się&nbsp;też sekcje z metodami (opis inspiracji i narzędzi użytych do stworzenia danego projektu) oraz polemika (w jaki sposób go dalej rozwijać, czy moje pierwotne założenia udało się spełnić, itd.).</p>
<h3 id="źródła"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#źródła">Źródła</a></h3>
<p>Niemniej to nie forma jest głównym wyznacznikiem “naukowości”. Według mnie o wiele ważniejszym jest odwoływanie się do źródeł. W końcu szansa, że opisałem coś jako pierwszy w Internecie, jest niemalże zerowa. Nawet jeśli w swoim zakątku Sieci jestem faktycznie pierwszy, to przecież o jakimś standardzie sieciowym napisała przede mną choćby… osoba pisząca jego specyfikację. Nawet jeśli już wymyślę&nbsp;coś absolutnie swojego (jak <a href="https://blog.comandeer.pl/headings-first-principle"><span lang="en">Headings First Principle</span> (HFP)</a>), to przecież to nie istnieje w próżni. Żeby mogło powstać HFP, musiał powstać HTML, a w nim nagłówki. Niezbędnymi elementami były też wszelkie <a href="https://www.smashingmagazine.com/2022/07/article-section-elements-accessibility/" rel="noreferrer noopener">dobre praktyki wokół dzielenia treści stron WWW na sekcje</a> oraz <a href="https://webaim.org/projects/screenreadersurvey10/#finding" rel="noreferrer noopener">sposób nawigowania przy pomocy nagłówków przez osoby korzystające z czytników ekranowych</a>. Słowem: musiał istnieć cały skomplikowany ekosystem Sieci, żebym ja mógł do niego dodać swoją małą&nbsp;cegiełkę.</p>
<p>Chyba najdobitniej widać to w przypadku mojego, jak dotąd najambitniejszego, projektu, <a href="https://gwd.comandeer.pl/" rel="noreferrer noopener">GWD</a>. W każdym “rozdziale” na końcu znajdują&nbsp;się listy źródeł oraz dodatkowych materiałów. Nazbierało się ich <strong>około tysiąca</strong>. A i tak była to mocno okrojona lista. Zwłaszcza, że sporo tematów ostatecznie wypadło z tego eseju.</p>
<p>Niemniej samo odesłanie do źródeł to za mało. Prawdziwa praca polega na odpowiednim ich dobraniu. Bo można opisać np. element HTML <code>bdo</code> i odesłać do jakiejś strony-krzak czy <a href="https://www.webkrytyk.pl/2022/10/30/w3schools-com-runda-druga/" rel="noreferrer noopener">popularnej, acz słabej jakości witryny</a>. Zamiast tego wypada jednak dokładnie zapoznać się, do czego linkujemy, czy nie ma tam błędów merytorycznych, czy poruszone są wszystkie kwestie ważne poruszenia, itd. I tak jak w świecie nauki, w którym mamy tytuły godne zaufania (<a href="https://www.science.org/" rel="noreferrer noopener">Science</a>, <a href="https://www.nature.com/" rel="noreferrer noopener">Nature</a>), tak i w webdevie jedne źródła są bezpieczniejsze od innych. Najbezpieczniejsze są, rzecz jasna, specyfikacje i standardy. Nie ma pewniejszych źródeł od nich. Jeśli chcę opisać element <code>bdo</code>, to <a href="https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-bdo-element" rel="noreferrer noopener">specyfikacja HTML</a> jest Kanonicznym Źródłem Prawdy™. Ale standardy często są pisane w sposób całkowicie hermetyczny i niezrozumiały dla dowolnej osoby, która nie czyta sobie RFC do poduszki. Wtedy warto sięgnąć po drugie najlepsze źródło – <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/bdo" rel="noreferrer noopener">dokumentację MDN</a>. Jeśli coś można by nazwać <q>oficjalną dokumentacją platformy sieciowej</q>, to byłoby to właśnie MDN.</p>
<p>Dobrej jakości źródeł jest oczywiście więcej – jak choćby <a href="https://www.smashingmagazine.com/" rel="noreferrer noopener">Smashing Magazine</a>, <a href="https://alistapart.com/" rel="noreferrer noopener">A List Apart</a> czy blogi znanych osób ze światka webdevu. I często na tych stronach, oderwanych od “oficjalnego obiegu”, dzieją się rzeczy ciekawsze. To w końcu na łamach A List Apart powstał termin <a href="https://alistapart.com/article/responsive-web-design/" rel="noreferrer noopener">Responsive Web Design</a>! Często też wyłuskać można na jakiejś stronie jakąś perełkę wśród całej reszty <em>meh</em> artykułów (hej, to zupełnie jak na moim blogu!). Tylko no właśnie – <em>wyłuskać</em>…</p>
<p>Sprawne posługiwanie się źródłami wymaga mimo wszystko wprawy i pewnego doświadczenia. Ja w swoim życiu przerobiłem już <em>tysiące</em> artykułów o webdevie i jestem w stanie oddzielić te faktycznie ciekawe od tych, które takie nie są. I właśnie tę umiejętność selekcji uważam za kluczową w bardziej naukowym podejściu do webdevu. Żeby ją zdobyć, to nie ma innej rady – trzeba spędzić swoje na czytaniu. Na początku będzie się czytać wszystko, jak leci, by z czasem odrzucać te najgorsze z najgorszych, aż w końcu nadejdzie moment, w którym się Po Prostu Wie™, co jest warte poświęcenia czasu, a co nie.</p>
<p>Niemniej z selekcją źródeł jest jeszcze jeden mały haczyk: ryzyko wpadnięcia w bańkę. Każda osoba ma jakieś poglądy i przekonania. Choćby tak błahe, jak nielubienie Reacta czy skłonność do wybierania mniej popularnych rozwiązań. I takie przekonania będą – bardziej lub mniej świadomie – wpływać na to, które źródła się dobiera. Nie sądzę, by dało się od tego całkowicie uciec, ale im bardziej się jest tego świadomym, tym mniejszy ma to wpływ na proces selekcji. Bo czasami warto wręcz sięgać po źródła, z którymi się nie zgadzamy – choćby po to, by <a href="https://gwd.comandeer.pl/progressive-enhancement/#krytyka" rel="noreferrer noopener">wejść&nbsp;z nimi w polemikę</a>.</p>
<h3 id="empiryczność"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#empiryczność">Empiryczność</a></h3>
<p>Wreszcie ostatni element, który widać w większości moich artykułów – zarówno tutaj, jak i <a href="https://www.webkrytyk.pl/2021/10/31/wpadki-i-wypadki-14/" rel="noreferrer noopener">na WebKrytyku</a>. Empiryczność, czyli mówiąc inaczej: eksperymentowanie. Kiedy opisuję nowe API, fajnie byłoby, gdybym przygotował choćby krótkie demko, w którym bym zademonstrował jego podstawowe ficzery. Tym prostym sposobem można być jak Longinus Podbipięta i <s>ściąć trzy gł</s> odhaczyć trzy rzeczy:</p>
<ol>
<li>pokazujemy osobom czytającym,&nbsp;że opisywane przez nas rzeczy faktycznie istnieją i działają,</li>
<li>tworzymy dla siebie samych z przyszłości źródło do linkowania, jeśli kiedyś wspomnimy znowu o tym API,</li>
<li>zapamiętujemy całą rzecz lepiej, niż gdybyśmy naskrobali jedynie teoretyczny opis.</li>
</ol>
<p>Poza tym – ostatecznie piszę ten blog dla przyjemności. A gdzie byłby cały fun z tego, gdybym się nie bawił kodem?</p>
<h2 id="tylko-po-co"><a class="header-anchor" href="https://blog.comandeer.pl/nullius-in-verba#tylko-po-co">Tylko po co?</a></h2>
<p>Odpowiedziałem już na pytanie, <q>jak</q> traktować webdev bardziej naukowo. Pora zatem pochylić się nad drugim, równie ważnym – jeśli nawet nie ważniejszym – pytaniem: <q>po co</q>?</p>
<p>Bo uważam, że tego wymaga uczciwość&nbsp;intelektualna. Webdev nie jest jakąś bliżej niezdefiniowaną sztuką okultystyczną (mimo że czasami tak się jawi!). Wbrew pozorom zdecydowana większość platformy sieciowej jest oparta na konkretnych standardach i specyfikacjach. Bo gdyby nie była, strony WWW działałyby w jednych przeglądarkach i nie działały w innych. <a href="https://thehistoryoftheweb.com/browser-wars/" rel="noreferrer noopener">Te czasy już przerabialiśmy</a> i to właśnie dzięki nim dzisiaj mamy otwarte standardy sieciowe. Jasne, nie jest idealnie, ale inicjatywy takie jak <a href="https://wpt.fyi/" rel="noreferrer noopener">Web Platform Tests</a> czy <a href="https://web.dev/blog/interop-2025" rel="noreferrer noopener">Interop</a> dają nadzieję, ze będzie lepiej. A skoro Sieć oparta jest na fundamentach, które są skodyfikowane (spisane “na papierze”, z przybitą pieczątką) i możliwe do empirycznej weryfikacji (w każdej chwili mogę odpalić przeglądarkę i sprawdzić, jak ten HTML czy CSS działają), to nie widzę powodu, by moje artykuły się do tego nie odwoływały.</p>
<p>Uczciwość intelektualna wymaga też, by istniała pewna ciągłość między tym, co tworzymy, a tym, co zostało stworzone. Tak jak artykuł naukowy nie może istnieć bez źródeł, tak trudno stworzyć artykuł o hipertekście bez… hipertekstu. W webdevie nie ma <a href="https://en.wikipedia.org/wiki/Self-made_man" rel="noreferrer noopener"><i lang="en">self-made men</i></a>, wszyscy budujemy na fundamentach postawionych przez innych. Nawet jeśli nasze budowanie polega na niezgadzaniu się z już istniejącymi dobrymi praktykami i proponowaniu czegoś nowego. Bo ostatecznie nie byłoby tej propozycji, gdyby nie ta praktyka! Żeby się z czymś nie zgadzać, trzeba najpierw zauważyć istnienie tego czegoś. Inaczej <a href="https://www.webkrytyk.pl/2021/12/15/video-swiete-wojny-javascriptu-ile-h1-na-stronie-i-dlaczego-kuba-przespal-ostatnie-10-lat/" rel="noreferrer noopener">nasza krytyka będzie całkowicie jałowa</a>.</p>
<p>Wreszcie: webdev najzwyczajniej w świecie <em>zasługuje</em> na bardziej naukowe – czy też po prostu merytoryczne – traktowanie. Choćby dlatego, że Sieć stanowi coraz większą część&nbsp;naszego codziennego życia. To wszechobecna technologia, do której <a href="https://www.itu.int/en/mediacentre/Pages/PR-2025-11-17-Facts-and-Figures.aspx" rel="noreferrer noopener">dostęp ma <strong>ok. 6 miliardów ludzi</strong></a>. W innych dziedzinach nauki i technologii, które często dotykają mniejszej liczby osób (jak np. projekt nowego skafandra astronautycznego), zachowujemy niezwykłą precyzję i naukową rygorystyczność. Natomiast w dziedzinie, która w taki czy inny sposób zahacza o <em>jakieś ¾ światowej populacji</em>, mam wrażenie, że często wychodzimy z założenia, że <q>to tylko technologia</q>. A to przecież nieprawda. <a href="https://shkspr.mobi/blog/2021/01/the-unreasonable-effectiveness-of-simple-html/" rel="noreferrer noopener">Ludzie mają różne powody, by korzystać z Internetu</a>. Jeśli – jako osoby tworzące strony WWW – nie traktujemy Sieci wystarczająco poważnie, ostatecznie szkodzimy osobom, które z tych stron WWW następnie korzystają. I myślę, że przestawienie myślenia z <q>to tylko technologia</q> w kierunku <q>webdev to nauka</q> jest mało bolesnym sposobem na stworzenie lepszej Sieci.</p>
<p>I tym optymistycznym akcentem życzę Wszystkim wesołych świąt 🎄🎄🎄!</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Dobry ziomek system</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/dobry-ziomek-system" rel="alternate" type="text/html"/>
			<published>2025-12-22T23:00:00.000Z</published>
			<updated>2025-12-22T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/dobry-ziomek-system</id>
			
				<summary><![CDATA[Systemy operacyjne wcale nie są takie najgorsze, jeśli chodzi o dostępność.]]></summary>
			
			<content type="html"><![CDATA[<p>Dzisiaj chciałbym zwrócić uwagę na cichego bohatera dostępności: system operacyjny! Dostarcza on bowiem szeregu technologii asystujących oraz innych usprawnień dla osób z niepełnosprawnościami.</p>
<h2 id="technologia-asystująca"><a class="header-anchor" href="https://blog.comandeer.pl/dobry-ziomek-system#technologia-asystująca">Technologia asystująca</a></h2>
<p>Czym jednak jest technologia asystująca? Za <a href="https://www.who.int/news-room/fact-sheets/detail/assistive-technology" rel="noreferrer noopener">definicją proponowaną przez WHO</a>:</p>
<blockquote>
<p lang="en">Assistive products help maintain or improve an individual’s functioning related to cognition, communication, hearing, mobility, self-care and vision, thus enabling their health, well-being, inclusion and participation.</p>
<p>[Produkty asystujące pomagają w utrzymaniu lub poprawie funkcjonowania osoby w zakresie funkcji poznawczych, komunikacji, słuchu, mobilności, samoopieki oraz wzroku, pozwalając tej osobie być zdrową fizycznie i psychicznie, włączoną w życie społeczne oraz uczestniczącą w nim.]</p>
</blockquote>
<p>Ta definicja jest niezwykle szeroka i obejmuje tak naprawdę wszystkie sprzęty oraz oprogramowanie, które pomagają w codziennym funkcjonowaniu. Stąd można do niej zaliczyć np. wózek inwalidzki, czytniki ekranu, ale też choćby lupę. Swego czasu <a href="https://gwd.comandeer.pl/dostepnosc/technologia-asystujaca/" rel="noreferrer noopener">opisałem trochę przykładów takich technologii</a>.</p>
<h2 id="systemowa-technologia-asystująca"><a class="header-anchor" href="https://blog.comandeer.pl/dobry-ziomek-system#systemowa-technologia-asystująca">Systemowa technologia asystująca</a></h2>
<p>Systemy operacyjne dostarczają osobom z nich korzystających wbudowanych technologii asystujących. Lista takich technologii będzie się różnić w zależności od systemu. Weźmy jako przykład macOS-a. Chyba najbardziej znaną technologią asystującą dostępną wraz z tym systemem jest <a href="https://en.wikipedia.org/wiki/VoiceOver" rel="noreferrer noopener">czytnik ekranowy VoiceOver</a>. Nie jest bynajmniej jedyną. Ustawienia systemu posiadają mocno rozbudowaną sekcję poświęconą dostępności:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/dobry-ziomek-system/macos-ustawienia-1360w.avif"><picture><source type="image/avif" srcset="/assets/images/dobry-ziomek-system/macos-ustawienia-440w.avif 440w, /assets/images/dobry-ziomek-system/macos-ustawienia-880w.avif 880w, /assets/images/dobry-ziomek-system/macos-ustawienia-1024w.avif 1024w, /assets/images/dobry-ziomek-system/macos-ustawienia-1360w.avif 1360w" sizes="90vw"><source type="image/webp" srcset="/assets/images/dobry-ziomek-system/macos-ustawienia-440w.webp 440w, /assets/images/dobry-ziomek-system/macos-ustawienia-880w.webp 880w, /assets/images/dobry-ziomek-system/macos-ustawienia-1024w.webp 1024w, /assets/images/dobry-ziomek-system/macos-ustawienia-1360w.webp 1360w" sizes="90vw"><source type="image/jpeg" srcset="/assets/images/dobry-ziomek-system/macos-ustawienia-440w.jpeg 440w, /assets/images/dobry-ziomek-system/macos-ustawienia-880w.jpeg 880w, /assets/images/dobry-ziomek-system/macos-ustawienia-1024w.jpeg 1024w, /assets/images/dobry-ziomek-system/macos-ustawienia-1360w.jpeg 1360w" sizes="90vw"><img src="/assets/images/dobry-ziomek-system/macos-ustawienia-1360w.jpeg" width="1360" height="1200" style="aspect-ratio: 1360 / 1200" alt="Aplikacja Ustawienia, zakładka &quot;Accessibility&quot;;&nbsp;w sekcji &quot;Vision&quot;&nbsp;znajdują się opcje &quot;VoiceOver&quot;, &quot;Zoom&quot;, &quot;Hover Text&quot;, &quot;Display&quot;, &quot;Motion&quot;, &quot;Read &amp; Speak&quot;, &quot;Audio Descriptions&quot;; w sekcji &quot;Hearing&quot;&nbsp;znajdują się opcje &quot;Hearing Devices&quot; oraz &quot;Audio&quot;; więcej opcji jest niewidocznych." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Sekcja ustawień dostępności</p></figcaption></figure>
<p>Jak widać, macOS dzieli te ustawienia na poszczególne zmysły, które dane opcje mają wspierać. Stąd w kategorii wzroku mamy <a href="http://m.in" rel="noreferrer noopener">m.in</a>. czytnik ekranu (VoiceOver), ale też ustawienia wyświetlacza czy animacji ruchowych, w słuchu – ustawienia dźwięku itd. Każda z tych opcji w menu posiada konkretne ustawienia, np. odnośnie animacji ruchowych:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/dobry-ziomek-system/macos-ruch-1360w.avif"><picture><source type="image/avif" srcset="/assets/images/dobry-ziomek-system/macos-ruch-440w.avif 440w, /assets/images/dobry-ziomek-system/macos-ruch-880w.avif 880w, /assets/images/dobry-ziomek-system/macos-ruch-1024w.avif 1024w, /assets/images/dobry-ziomek-system/macos-ruch-1360w.avif 1360w" sizes="90vw"><source type="image/webp" srcset="/assets/images/dobry-ziomek-system/macos-ruch-440w.webp 440w, /assets/images/dobry-ziomek-system/macos-ruch-880w.webp 880w, /assets/images/dobry-ziomek-system/macos-ruch-1024w.webp 1024w, /assets/images/dobry-ziomek-system/macos-ruch-1360w.webp 1360w" sizes="90vw"><source type="image/jpeg" srcset="/assets/images/dobry-ziomek-system/macos-ruch-440w.jpeg 440w, /assets/images/dobry-ziomek-system/macos-ruch-880w.jpeg 880w, /assets/images/dobry-ziomek-system/macos-ruch-1024w.jpeg 1024w, /assets/images/dobry-ziomek-system/macos-ruch-1360w.jpeg 1360w" sizes="90vw"><img src="/assets/images/dobry-ziomek-system/macos-ruch-1360w.jpeg" width="1360" height="1200" style="aspect-ratio: 1360 / 1200" alt="Aplikacja Ustawienia, zakładka &quot;Accessibility&quot;, podsekcja &quot;Motion&quot;; dostępne utawienia: &quot;Reduce motion&quot;, &quot;Dim flashing lights&quot;, &quot;Auto-play animated images&quot;, &quot;Prefer non-blinking cursor&quot;, &quot;Vehicle Motion Cues&quot;." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Sekcja ustawień dostępności dotyczących animacji ruchowych</p></figcaption></figure>
<p>System od Apple pozwala na dostosowanie większości aspektów systemu, np. wyłączając autoodtwarzanie ruchomych obrazków (GIF-ów). I choć na początku ogrom opcji może przytłaczać, to sam fakt ich istnienia jest jak najbardziej pozytywny. W końcu jedne z najbardziej dostępnych rozwiązań to te, które można <a href="https://gwd.comandeer.pl/inkluzywnosc/preferencje-osoby-uzytkowniczej/" rel="noreferrer noopener">najbardziej dostosować pod siebie</a>!</p>
<p>Windows również posiada dość rozbudowane opcje dostępności. Podobnie jak macOS udostępnia czytnik ekranowy, <a href="https://www.microsoft.com/en-us/windows/tips/narrator" rel="noreferrer noopener">Narratora</a>. W przypadku jednak tego systemu nie zdobył on nigdy większej popularności. Od lat na tej platformie królują <a href="https://vispero.com/jaws-screen-reader-software/" rel="noreferrer noopener">JAWS</a> (płatny) oraz <a href="https://www.nvaccess.org/about-nvda/" rel="noreferrer noopener">NVDA</a> (darmowy). Oprócz czytnika Windows oferuje choćby <a href="https://support.microsoft.com/pl-pl/windows/u%C5%BCywanie-lupy-do-zapewnienia-lepszej-widoczno%C5%9Bci-element%C3%B3w-na-ekranie-414948ba-8b1c-d3bd-8615-0e5e32204198" rel="noreferrer noopener">lupę ekranową</a> (do powiększania fragmentów ekranu) czy <a href="https://blog.comandeer.pl/czy-div-jest-dostepny#dost%C4%99pno%C5%9B%C4%87-to-nie-tylko-czytniki-ekranowe">tryb wysokiego kontrastu</a>.</p>
<p>Systemy mobilne także dostarczają technologii asystujących. Zarówno Android, jak i iOS, mają wbudowane czytniki ekranowe – odpowiednio <a href="https://support.google.com/accessibility/android/answer/6007100?hl=en" rel="noreferrer noopener">TalkBack</a> i VoiceOver. Mają także choćby opcję ograniczania animacji ruchowych. Także sporo linuksowych dystrybucji ma ustawienia dostępności, np. LInux Mint pozwala włączyć tryb wysokiego kontrastu i ma wbudowany czytnik ekranu (<a href="https://orca.gnome.org/" rel="noreferrer noopener">Orcę</a>).</p>
<p>Innymi słowy: systemy operacyjne próbują dostarczyć niezbędnych narzędzi do tego, by mogło z nich korzystać jak najwięcej osób, w tym osoby z niepełnosprawnościami.</p>
<h2 id="system-a-przeglądarka"><a class="header-anchor" href="https://blog.comandeer.pl/dobry-ziomek-system#system-a-przeglądarka">System a przeglądarka</a></h2>
<p>Jak zatem taka systemowa technologia asystująca przekłada się na przeglądarkę? To zależy. Wraz z rozwojem technologii sieciowych, osoby tworzące strony WWW dostały możliwość dowiedzenia się nieco więcej o systemie operacyjnym, na którym uruchomiona jest przeglądarka. Najczęściej dzięki nowym media queries w CSS-ie. Wśród nich wymienić można:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion" rel="noreferrer noopener"><code>prefers-reduced-motion</code></a> – informujące o ustawieniach dotyczących ograniczenia animacji ruchowych,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/forced-colors" rel="noreferrer noopener"><code>forced-colors</code></a> – informujące o trybie wymuszonych kolorów, czyli ograniczeniu palety dostępnych kolorów, jak w przypadku Windowsowego trybu wysokiego kontrastu,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-color-scheme" rel="noreferrer noopener"><code>prefers-color-scheme</code></a> – informujące o preferencjach osoby użytkowniczej co do doboru schematu kolorystycznego; inaczej mówiąc: czy woli jasny, czy ciemny motyw.</li>
</ul>
<p>Część z systemowych ułatwień dostępu może być jednak “zakamuflowana”. Dobrym przykładem są tu różnego rodzaju powiększenia. Działają one trochę jak zmniejszenie rozdzielczości: poszczególne elementy robią się większe, przez co są bardziej czytelne. Równocześnie jednak ogranicza to liczbę elementów widocznych w danej chwili na ekranie. Przeglądarka często interpretuje to jako mniejszy <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/CSSOM_view/Viewport_concepts" rel="noreferrer noopener">viewport</a>. Co też dobrze pokazuje, jak zawodne jest opieranie responsywności na założeniu <q>mały ekran = urządzenie mobilne</q>.</p>
<p>Jeszcze inne technologie asystujące są całkowicie przezroczyste dla przeglądarki, jak np. czytniki ekranu. Nie ma żadnego sposobu, aby dowiedzieć się, że osoba odwiedzająca stronę używa czytnika ekranu. I to <a href="https://axesslab.com/digital-apartheid/" rel="noreferrer noopener">akurat dobrze</a>. Zwłaszcza, że stworzenie strony z zachowaniem standardów sieciowych i dobrych praktyk dostępności jest w większości przypadków wystarczające, by strona ta działała poprawnie we wszystkich popularnych czytnikach ekranu. Innymi słowy: wystarczy <em>dobrze</em> wykonywać swoją pracę – jakkolwiek brutalnie by to nie brzmiało.</p>
<p>Podsumowując: przeglądarka i system to para zaufanych przyjaciół, która niektóre sekrety zostawia wyłącznie dla siebie. Ale to, czym się jednak z nami dzielą, powinno wystarczyć do stworzenia dostępnej, przyjaznej strony WWW.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Ale kanał…</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/ale-kanal" rel="alternate" type="text/html"/>
			<published>2025-12-21T23:00:00.000Z</published>
			<updated>2025-12-21T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/ale-kanal</id>
			
				<summary><![CDATA[Pora nieco ulepszyć kolejny element bloga – kanały Atom.]]></summary>
			
			<content type="html"><![CDATA[<p>Dzisiaj nadeszła pora na kolejny element bloga, który już od dłuższego czasu czekał na poprawki: kanały Atom.</p>
<h2 id="kanały-atom"><a class="header-anchor" href="https://blog.comandeer.pl/ale-kanal#kanały-atom">Kanały Atom</a></h2>
<p>Dawno, dawno temu ktoś wpadł na szalony pomysł: a co jeśliby pozwolić ludziom czytać treść ze stron internetowych <em>jak i kiedy chcą</em>? Zamiast musieć wchodzić na konkretną&nbsp;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. <a href="https://en.wikipedia.org/wiki/Web_feed" rel="noreferrer noopener"><i lang="en">feeds</i> (kanały)</a>. W największym skrócie: kanał to plik w formacie innym niż HTML, w którym znajduje się&nbsp;treść strony i który jest przystosowany do czytania przez tzw. <i lang="en">feed readers</i> (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.</p>
<p>Obecnie mamy trzy standardy kanałów: <a href="https://www.rssboard.org/rss-specification" rel="noreferrer noopener"><span lang="en">Really Simple Syndication</span> (RSS)</a>, <a href="https://www.ietf.org/rfc/rfc4287.txt" rel="noreferrer noopener">Atom</a> oraz <a href="https://www.jsonfeed.org/version/1.1/" rel="noreferrer noopener">JSON Feed</a>. Te dwa pierwsze to formaty oparte o XML, podczas gdy ostatni, jak sama nazwa sugeruje, o JSON. Na tym blogu stosuję format Atom.</p>
<h2 id="problemy-z-kanałem"><a class="header-anchor" href="https://blog.comandeer.pl/ale-kanal#problemy-z-kanałem">Problemy z kanałem</a></h2>
<p>Przy okazji <a href="https://blog.comandeer.pl/blog-2.0">odświeżenia bloga</a> dodałem też do nagłówków w postach linki odsyłające do konkretnych sekcji:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">h2</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> id</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"odświeżone-bloczki"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 2 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">a</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"header-anchor"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> href</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#odświeżone-bloczki"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Odświeżone bloczki&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">a</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 1 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">h2</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Zawartość nagłówka jest otoczona linkiem (1), który odsyła do tego nagłówka przy pomocy <a href="https://www.kurshtml.edu.pl/html/do_etykiety,odsylacze.html" rel="noreferrer noopener">kotwicy</a> (2). Problem polega na tym, że atrybut <code>[href]</code> 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…</p>
<div class="note" role="note" aria-labelledby="note-67"><p class="note__label" id="note-67">Dygresja</p><div class="note__content"><p>Jeśli zastanawiasz się, czemu wybrałem taki sposób linkowania sekcji, polecam <a href="https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/" rel="noreferrer noopener">artykuł Amber Wilson o dostępnych kotwicach</a>.</p></div></div>
<p>…aż w końcu zmieniłem też bloczki kodu i do problemu z nagłówkami doszedł kolejny:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/ale-kanal/problem-672w.avif"><picture><source type="image/avif" srcset="/assets/images/ale-kanal/problem-440w.avif 440w, /assets/images/ale-kanal/problem-672w.avif 672w" sizes="90vw"><source type="image/webp" srcset="/assets/images/ale-kanal/problem-440w.webp 440w, /assets/images/ale-kanal/problem-672w.webp 672w" sizes="90vw"><source type="image/png" srcset="/assets/images/ale-kanal/problem-440w.png 440w, /assets/images/ale-kanal/problem-672w.png 672w" sizes="90vw"><img src="/assets/images/ale-kanal/problem-672w.png" width="672" height="377" style="aspect-ratio: 672 / 377" alt="Bloczek kodu HTML, nad którym znajduje się tekst &quot;HTML Kopiuj&quot;." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Fragment wpisu <a href="https://blog.comandeer.pl/inkunabuly-madrosci">Inkunabuły mądrości</a> w czytniku kanałów</p></figcaption></figure>
<p>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ć!</p>
<h2 id="naprawa-html-a"><a class="header-anchor" href="https://blog.comandeer.pl/ale-kanal#naprawa-html-a">Naprawa HTML-a</a></h2>
<p>Postanowiłem dodać <a href="https://www.11ty.dev/docs/filters/" rel="noreferrer noopener">filtr w Eleventy</a>, 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ż&nbsp;sparsowana z Markdowna do HTML-a. A to oznacza, że muszę parsować HTML. W tym celu postanowiłem użyć <a href="https://www.npmjs.com/package/cheerio" rel="noreferrer noopener">biblioteki cheerio</a>, która dostarcza API podobnego do <a href="https://jquery.com/" rel="noreferrer noopener">jQuery</a>. Ostatecznie powstał następujący filtr:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { load } </span><span style="color:#D73A49;--shiki-dark:#F97583">from</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'cheerio'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> MORE_COMMENT_REGEX</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#032F62;--shiki-dark:#DBEDFF">&lt;!--</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#032F62;--shiki-dark:#DBEDFF">more</span><span style="color:#005CC5;--shiki-dark:#79B8FF">\s</span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#032F62;--shiki-dark:#DBEDFF">--&gt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 10</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">eleventyConfig.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addFilter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'rss_content'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">post</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> postURL</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> cfUrl</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#D73A49;--shiki-dark:#F97583">new</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> URL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( post.data.permalink, post.data.site.url ).href ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> $</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> load</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( post.content ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	$</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">':where(h1, h2, h3, h4, h5, h6) a'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">each</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">link</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> $link</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> $</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( link );</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> originalHref</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> $link.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">attr</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'href'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		$link.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">attr</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'href'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, postURL </span><span style="color:#D73A49;--shiki-dark:#F97583">+</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> originalHref ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	} );</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	$</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code__copy'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">remove</span><span style="color:#24292E;--shiki-dark:#E1E4E8">();</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> $</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'body'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#005CC5;--shiki-dark:#79B8FF">MORE_COMMENT_REGEX</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 9</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Dodajemy nowy filtr o nazwie <code>rss_content</code> (1). Przekazujemy mu <a href="https://www.11ty.dev/docs/data-eleventy-supplied/#page-variable" rel="noreferrer noopener">obiekt wpisu</a>. Następnie importujemy <a href="https://cheerio.js.org/docs/api/variables/load" rel="noreferrer noopener">funkcję <code>load()</code></a> z biblioteki <code>cheerio</code> (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 <code>[href]</code> (5) oraz zamieniamy go na absolutny URL (6) – a więc <code>#odświeżone-bloczki</code> zamienią się na <code>https://blog.comandeer.pl/inkunabuly-madrosci#odświeżone-bloczki</code>. Absolutny URL uzyskujemy, sklejając link do posta z linkiem do strony przy pomocy <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL" rel="noreferrer noopener">konstruktora <code>URL</code></a> (7). Link do posta jest dostarczony przez Eleventy, natomiast link do strony – <a href="https://github.com/Comandeer/blog/blob/7b5582136be29b149646515c5b355ccb2b6c6b7d/src/_data/site.js#L3" rel="noreferrer noopener">przeze mnie</a> przy pomocy mechanizmu <a href="https://www.11ty.dev/docs/data-global/" rel="noreferrer noopener">globalnych plików z danymi</a>. Po naprawieniu linków, wyszukujemy i <a href="https://cheerio.js.org/docs/api/classes/Cheerio#remove" rel="noreferrer noopener">usuwamy</a> wszystkie przyciski do kopiowania kodu (8). Na koniec <a href="https://cheerio.js.org/docs/api/classes/Cheerio#html" rel="noreferrer noopener">wyciągamy kod HTML</a> z DOM-u i usuwamy z niego komentarz <code>&lt;!--more--&gt;</code> (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 <code>body</code>. Biblioteka cheerio parsuje bowiem HTML zgodnie z regułami opisanymi w specyfikacji, a więc – <a href="https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring" rel="noreferrer noopener">jako pełny dokument</a>, co sprawia, że dodaje brakujące elementy <code>html</code> i <code>body</code>. Można też wymusić na cheerio <a href="https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-parsing-algorithm-steps" rel="noreferrer noopener">parsowanie w trybie fragmentu</a>, podając <code>false</code> jako trzeci argument do funkcji <code>load()</code>.</p>
<div class="note" role="note" aria-labelledby="note-68"><p class="note__label" id="note-68">Dygresja</p><div class="note__content"><p>Komentarz <code>&lt;!--more--&gt;</code> to technika, którą ukradłem z… <a href="https://codex.wordpress.org/Customizing_the_Read_More" rel="noreferrer noopener">WordPressa</a>. 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 <q>Czytaj więcej</q>, prowadzącym do podstrony z już pełną wersją artykułu.</p></div></div>
<p>Tajemnicza funkcja <code>cfUrl()</code> z poprzedniego fragmentu kodu wycina z przekazanego linku rozszerzenie <code>.html</code> lub fragment <code>index.html</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> cfUrl</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> url.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#032F62;--shiki-dark:#DBEDFF">index</span><span style="color:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold">\.</span><span style="color:#032F62;--shiki-dark:#DBEDFF">html</span><span style="color:#D73A49;--shiki-dark:#F97583">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">replace</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /</span><span style="color:#22863A;--shiki-light-font-weight:bold;--shiki-dark:#85E89D;--shiki-dark-font-weight:bold">\.</span><span style="color:#032F62;--shiki-dark:#DBEDFF">html</span><span style="color:#D73A49;--shiki-dark:#F97583">$</span><span style="color:#032F62;--shiki-dark:#9ECBFF">/</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">''</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Dzięki temu linki typu <code>/blog-2.0.html</code> lub <code>/kategorie/javascript/index.html</code> zmienią się odpowiednio w <code>/blog-2.0</code> oraz <code>/kategorie/javascript</code>.</p>
<p>Tak stworzony filtr dodajemy następnie do szablonu kanałów, w miejscu, w którym wyświetlana jest treść posta:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">Liquid</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">content</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"html"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;&lt;![CDATA[</span><span style="color:#032F62;--shiki-dark:#9ECBFF">{{ post | rss_content }}</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]]&gt;&lt;/</span><span style="color:#B31D28;--shiki-light-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic">content</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>I to w sumie tyle! W końcu kanały bloga powinny działać poprawnie.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Inkunabuły mądrości</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/inkunabuly-madrosci" rel="alternate" type="text/html"/>
			<published>2025-12-20T23:00:00.000Z</published>
			<updated>2025-12-20T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/inkunabuly-madrosci</id>
			
				<summary><![CDATA[Poprawiłem bloczki kodu!]]></summary>
			
			<content type="html"><![CDATA[<p>Po odświeżeniu dygresji przyszedł czas na kolejny częsty element mojego bloga: bloczki z kodem!</p>
<h2 id="stare-bloczki"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#stare-bloczki">Stare bloczki</a></h2>
<p>Stare bloczki… były:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/inkunabuly-madrosci/stary-bloczek-725w.avif"><picture><source type="image/avif" srcset="/assets/images/inkunabuly-madrosci/stary-bloczek-440w.avif 440w, /assets/images/inkunabuly-madrosci/stary-bloczek-725w.avif 725w" sizes="90vw"><source type="image/webp" srcset="/assets/images/inkunabuly-madrosci/stary-bloczek-440w.webp 440w, /assets/images/inkunabuly-madrosci/stary-bloczek-725w.webp 725w" sizes="90vw"><source type="image/png" srcset="/assets/images/inkunabuly-madrosci/stary-bloczek-440w.png 440w, /assets/images/inkunabuly-madrosci/stary-bloczek-725w.png 725w" sizes="90vw"><img src="/assets/images/inkunabuly-madrosci/stary-bloczek-725w.png" width="725" height="173" style="aspect-ratio: 725 / 173" alt="Bloczek kodu CSS z ciemnego motywu bloga, otoczony białym obramowaniem." loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Przykład bloczka kodu z wpisu <a href="https://blog.comandeer.pl/frywolne-marginalia">Frywolne marginalia</a></p></figcaption></figure>
<p>Co prawda spełniały swoje zadanie, miały nawet dobre kolorowanie składni (dzięki <a href="https://shiki.matsu.io/" rel="noreferrer noopener">Shiki</a>), ale brakowało w nich choćby informacji o języku programowania. Dlatego też postanowiłem je nieco odświeżyc.</p>
<h2 id="odświeżone-bloczki"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#odświeżone-bloczki">Odświeżone bloczki</a></h2>
<p>Nowe bloczki wyglądają następująco:</p>
<figure class="figure"><a class="figure__link" href="/assets/images/inkunabuly-madrosci/nowy-bloczek-643w.avif"><picture><source type="image/avif" srcset="/assets/images/inkunabuly-madrosci/nowy-bloczek-440w.avif 440w, /assets/images/inkunabuly-madrosci/nowy-bloczek-643w.avif 643w" sizes="90vw"><source type="image/webp" srcset="/assets/images/inkunabuly-madrosci/nowy-bloczek-440w.webp 440w, /assets/images/inkunabuly-madrosci/nowy-bloczek-643w.webp 643w" sizes="90vw"><source type="image/png" srcset="/assets/images/inkunabuly-madrosci/nowy-bloczek-440w.png 440w, /assets/images/inkunabuly-madrosci/nowy-bloczek-643w.png 643w" sizes="90vw"><img src="/assets/images/inkunabuly-madrosci/nowy-bloczek-643w.png" width="643" height="209" style="aspect-ratio: 643 / 209" alt="Bloczek kodu CSS z ciemnego motywu bloga, nad którym znajduje się pasek z nazwą języka programowania oraz przyciskiem &quot;Kopiuj&quot;" loading="lazy" decoding="async"></picture></a><figcaption class="figure__caption"><p>Przykład odświezonego bloczka kodu z wpisu <a href="https://blog.comandeer.pl/frywolne-marginalia">Frywolne marginalia</a></p></figcaption></figure>
<p>Tak naprawdę zmiany nie są szczególnie wielkie – pojawiła się jedynie nazwa języka oraz przycisk do skopiowania kodu do schowka.</p>
<h3 id="nowy-html"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#nowy-html">Nowy HTML</a></h3>
<p>Od strony HTML-a cały bloczek kodu trafił do elementu <code>figure</code>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">HTML</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">figure</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> typeof</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"SoftwareSourceCode"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">figcaption</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__caption"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 3 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__title"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"programmingLanguage"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;CSS&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">span</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 1 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__copy"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;Kopiuj&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">button</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 2 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">figcaption</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> class</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"code__code"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> translate</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"no"</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> property</span><span style="color:#24292E;--shiki-dark:#E1E4E8">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 4 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">pre</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;&lt;</span><span style="color:#22863A;--shiki-dark:#85E89D">code</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;[…]&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">code</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">pre</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt; </span><span style="color:#6A737D;--shiki-dark:#6A737D">&lt;!-- 5 --&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">div</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">&lt;/</span><span style="color:#22863A;--shiki-dark:#85E89D">figure</span><span style="color:#24292E;--shiki-dark:#E1E4E8">&gt;</span></span>
<span class="line"></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Podpis z nazwą języka (1) i przyciskiem (2) został umieszczony w <code>figcaption</code> (3), natomiast sam kod w dodatkowym <code>div</code>ie z <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/translate" rel="noreferrer noopener">atrybutem <code>[translate=no]</code></a> (4). Dzięki temu Google Translate i podobne usługi nie będą próbowały tłumaczyć kodu na inne języki. Dodatkowo zastosowałem też <a href="https://blog.comandeer.pl/o-semantyce-slow-kilka#rozszerzanie-semantyki">RDFa</a>, żeby oznaczyć całość jako kod w języku programowania. Sam kod (5) pozostał bez zmian – dalej jest kolorowany przez Shiki.</p>
<h3 id="nowy-js"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#nowy-js">Nowy JS</a></h3>
<p>Przycisk do kopiowania kodu potrzebuje obsługi w JS-ie. Dodałem więc taką do swojego pliku JS:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">addEventListener</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'click'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#D73A49;--shiki-dark:#F97583">async</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">evt</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> isCopyButton</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> evt.target.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">closest</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code__copy'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#D73A49;--shiki-dark:#F97583">!</span><span style="color:#24292E;--shiki-dark:#E1E4E8">isCopyButton ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> closestCode</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> evt.target.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">closest</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ).</span><span style="color:#6F42C1;--shiki-dark:#B392F0">querySelector</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'.code__code'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	await</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> navigator.clipboard.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">writeText</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( closestCode.innerText ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Stosuje tutaj technikę <a href="https://javascript.info/event-delegation" rel="noreferrer noopener"><i lang="en">event delegation</i> (delegacji zdarzeń)</a> (1). Dzięki temu mogę przypiąć tylko jeden nasłuchiwacz i obsłużyć wszystkie przyciski “Kopiuj”, zamiast musieć się przypiąć do każdego z nich osobno. Niemniej ta technika sprawia, ze muszę sprawdzić, czy na pewno został kliknięty przycisk “Kopiuj”. Robię to przy pomocy wyszukania go przez <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/closest" rel="noreferrer noopener">metodę <code>#closest()</code></a> (2). Metoda ta zwróci najbliższy element o klasie <code>.code__copy</code>. W przypadku, gdy ktoś kliknie przycisk, będzie to on sam. Metoda <code>#closest()</code> zabezpiecza nas przed przypadkiem, gdybyśmy dodali kiedyś ikonkę do przycisku i ktoś kliknął w nią – wówczas zamiast przycisku mielibyśmy w <code>evt.target</code> np. element <code>svg</code>. Jak już ustalimy, że został naciśnięty odpowiedni przycisk, wyszukujemy najbliższy bloczek kodu (3). Następnie wywołujemy <a href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText" rel="noreferrer noopener">metodę <code>Clipboard#writeText()</code></a>, do której przekazujemy tekstową zawartość bloczka (4). Jeśli osoba naciskająca przycisk wyrazi zgodę na zapisanie danych do schowka, kod zostanie skopiowany.</p>
<h3 id="nowy-backend"><a class="header-anchor" href="https://blog.comandeer.pl/inkunabuly-madrosci#nowy-backend">Nowy backend</a></h3>
<p>Nowe bloczki kodu renderowane są przy pomocy autorskiego pluginu do <a href="https://www.npmjs.com/package/markdown-it" rel="noreferrer noopener">MarkdownIt</a> (biblioteki renderującej Markdowna w Eleventy). Jego kod prezentuje się następująco:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> markdownItCodeBlock</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">markdownIt</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> originalFenceRule</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> markdownIt.renderer.rules.fence; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	markdownIt.renderer.rules.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">fence</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ( </span><span style="color:#E36209;--shiki-dark:#FFAB70">tokens</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">idx</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">env</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">slf</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) </span><span style="color:#D73A49;--shiki-dark:#F97583">=&gt;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> token</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tokens[ idx ];</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> lang</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> token.info </span><span style="color:#D73A49;--shiki-dark:#F97583">?</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> token.info.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">trim</span><span style="color:#24292E;--shiki-dark:#E1E4E8">() </span><span style="color:#D73A49;--shiki-dark:#F97583">:</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> renderedCodeBlock</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> originalFenceRule</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( tokens, idx, options, env, slf ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		/</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 5</span><span style="color:#D73A49;--shiki-dark:#F97583"> */</span><span style="color:#D73A49;--shiki-dark:#F97583"> return</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> `&lt;figure class="code" typeof="SoftwareSourceCode"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;figcaption class="code__caption"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">				&lt;span class="code__title" property="programmingLanguage"&gt;${</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> langs</span><span style="color:#032F62;--shiki-dark:#9ECBFF">[ </span><span style="color:#24292E;--shiki-dark:#E1E4E8">lang</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ] </span><span style="color:#D73A49;--shiki-dark:#F97583">??</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ''</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }&lt;/span&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">				&lt;button class="code__copy"&gt;Kopiuj&lt;/button&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;/figcaption&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;div class="code__code" translate="no" property="text"&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">				${</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> renderedCodeBlock</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> }</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">			&lt;/div&gt;</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">		&lt;/figure&gt;`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	};</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Na początku zapisujemy sobie do zmiennej aktualną funkcję do renderowania bloczków kodu (1). W przypadku MarkdownIt funkcja ta znajduje się w <a href="https://markdown-it.github.io/markdown-it/#Renderer.prototype.rules" rel="noreferrer noopener">regułach renderera</a>. Następnie nadpisujemy tę regułę własną (2). Wyciągamy z kodu Markdown nazwę języka programowania (3), a następnie wywołujemy oryginalną regułę renderującą bloczki kodu (4). Dzięki temu dostajemy kod ładnie pokolorowany przez Shiki. Na koniec  tworzymy nasz własny kod HTML bloczka (5), do którego wsadzamy pokolorowany kod i zwracamy całość&nbsp;z funkcji.</p>
<p>Teraz zostaje tylko dodać ten plugin do <a href="https://github.com/Comandeer/blog/blob/983e2db060f90d4bcfc923187c2147d3c937e12d/plugins/markdownIt.js#L113" rel="noreferrer noopener">naszej zmodyfikowanej wersji MarkdownIt</a>. Można to zrobić przy pomocy <a href="https://markdown-it.github.io/markdown-it/#MarkdownIt.use" rel="noreferrer noopener">metody <code>#use()</code></a>.</p>
<p>I to tyle! Bloczki kodu na blogu są od teraz <em>nieco</em> lepsze.</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Równając w dół</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/rownajac-w-dol" rel="alternate" type="text/html"/>
			<published>2025-12-20T16:11:00.000Z</published>
			<updated>2025-12-20T16:11:00.000Z</updated>
			<id>https://blog.comandeer.pl/rownajac-w-dol</id>
			
				<summary><![CDATA[Kompatybilność międzyprzeglądarkowa to spory problem. Baseline ma pomóc go rozwiązać]]></summary>
			
			<content type="html"><![CDATA[<p>Kompatybilność międzyprzeglądarkowa od lat jest <a href="https://2025.stateofhtml.com/en-US/usage/" rel="noreferrer noopener">jednym z największych problemów webdevu</a>. Dlatego powstał Baseline, który ma pomóc ten problem rozwiązać.</p>
<h2 id="kompatybilność"><a class="header-anchor" href="https://blog.comandeer.pl/rownajac-w-dol#kompatybilność">Kompatybilność?</a></h2>
<p>Obecnie na rynku mamy trzy duże silniki przeglądarek: <a href="https://en.wikipedia.org/wiki/Blink_(browser_engine)" rel="noreferrer noopener">Blink</a> (używany w Chrome), <a href="https://en.wikipedia.org/wiki/WebKit" rel="noreferrer noopener">WebKit</a> (Safari) oraz <a href="https://en.wikipedia.org/wiki/Gecko_(software)" rel="noreferrer noopener">Gecko</a> (Firefox). Do tego dochodzą różne przeglądarki oparte na różnych wersjach tych trzech silników, ale też np. rozwiązania pokroju <a href="https://en.wikipedia.org/wiki/Electron_(software_framework)" rel="noreferrer noopener">Electrona</a>, który pozwala tworzyć aplikacje desktopowe na Chromium. Powstają&nbsp;też dwa nowe, duże silniki: <a href="https://en.wikipedia.org/wiki/Servo_(software)" rel="noreferrer noopener">Servo</a> oraz <a href="https://en.wikipedia.org/wiki/Ladybird_(web_browser)" rel="noreferrer noopener">Ladybird</a>.</p>
<p>W teorii wszystkie te silniki wspierają&nbsp;te same standardy sieciowe. Ale w praktyce różnice mogą być znaczące. Po pierwsze, istnieje bardzo dużo standardów sieciowych, które nieustannie się zmieniają (np. <a href="https://html.spec.whatwg.org/multipage/" rel="noreferrer noopener">specyfikacja HTML</a> w momencie pisania tego artykułu była zmieniana ostatnio 17 grudnia 2025). Niektóre przeglądarki nie mają na tyle zasobów, by implementować wszystkie najnowsze standardy i robią&nbsp;to z opóźnieniem. Dodatkowo, nawet jeśli przeglądarka X zaimplementuje jakiś standard z wersji z listopada, to wersja z grudnia może być już&nbsp;zupełnie inna. Tym samym dwie przeglądarki implementujące ten sam standard mogą nie być kompatybilne między sobą.</p>
<p>Kolejnym czynnikiem są różne priorytety przeglądarek. Niektóre przeglądarki skupiają się na dostarczaniu coraz to nowych ficzerów, podczas gdy inne stawiają na innowacje w innych obszarach, jak np. UI samej przeglądarki. Dobrym przykładem może być tutaj <a href="https://www.chromium.org/teams/web-capabilities-fugu/" rel="noreferrer noopener">Projekt Fugu</a>. Google zgłosiło szereg propozycji standardów sieciowych, które miały zminimalizować różnice między możliwościami aplikacji natywnych oraz webowych. Wśród propozycji były <a href="http://m.in" rel="noreferrer noopener">m.in</a>. <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API" rel="noreferrer noopener">WebUSB</a> czy <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_NFC_API" rel="noreferrer noopener">Web NFC</a>. Zarówno <a href="https://mozilla.github.io/standards-positions/#webusb" rel="noreferrer noopener">Firefox</a>, jak i <a href="https://webkit.org/standards-positions/#position-68" rel="noreferrer noopener">Safari</a>, odmówiły implementacji tych propozycji, wskazując na potencjalne problemy z bezpieczeństwem i prywatnością.</p>
<p>Wreszcie: zarówno przeglądarki, jak i same specyfikacje, mają bugi. Dodatkowo specyfikacje można interpretować na różne sposoby, nawet jeśli włożono spory wysiłek w to, by ich tekst był jak najbardziej jednoznaczny. Tutaj na pomoc przychodzą <a href="https://wpt.fyi/results/" rel="noreferrer noopener">Web Platform Tests</a> – testy sprawdzające zgodność implementacji ze standardami sieciowymi. I choć dostarczają one dokładnych wyników, to są przeznaczone raczej dla osób, które zawodowo zajmują się tworzeniem przeglądarek lub standardów. Dla całej reszty mogą być mało czytelne.</p>
<h2 id="baseline"><a class="header-anchor" href="https://blog.comandeer.pl/rownajac-w-dol#baseline">Baseline</a></h2>
<p>Dlatego też powstał <a href="https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility" rel="noreferrer noopener">Baseline</a>. To wskaźnik <a href="https://web.dev/baseline/overview" rel="noreferrer noopener">pierwotnie zaproponowany przez Chrome’a w trakcie Google I/O 2023</a>. Obecnie jest rozwijany przez <a href="https://www.w3.org/community/webdx/" rel="noreferrer noopener">Web Developer Experience Community Group</a>. Jego zadaniem jest wskazywanie, czy dany ficzer jest już dostępny we wszystkich najpopularniejszych przeglądarkach, czyli w Chrome (na desktopie i Androdzie), Firefoksie (na desktopie i Androidzie), Edge’u (na desktopie) oraz Safari (na desktopie i iOS-ie).</p>
<p>Każdy ficzer może mieć trzy poziomy wsparcia:</p>
<ul>
<li><picture style="display: inline-block;block-size: 2rem;inline-size: 2rem;"><source type="image/svg+xml" srcset="/assets/images/rownajac-w-dol/limited-availability-540w.svg 540w" sizes="90vw"><img src="/assets/images/rownajac-w-dol/limited-availability-540w.svg" width="540" height="300" style="aspect-ratio: 540 / 300" alt="" loading="lazy" decoding="async"></picture> <b lang="en">Limited availability</b> (ograniczona dostępność) – dany ficzer wciąż nie jest dostępny we wszystkich przeglądarkach,</li>
<li><picture style="display: inline-block;block-size: 2rem;inline-size: 2rem;"><source type="image/svg+xml" srcset="/assets/images/rownajac-w-dol/newly-available-540w.svg 540w" sizes="90vw"><img src="/assets/images/rownajac-w-dol/newly-available-540w.svg" width="540" height="300" style="aspect-ratio: 540 / 300" alt="" loading="lazy" decoding="async"></picture> <b lang="en">Newly available</b> (świeżo dostępny) – dany ficzer jest dostępny w najnowszych wersjach przeglądarek, może nie działać w starszych,</li>
<li><picture style="display: inline-block;block-size: 2rem;inline-size: 2rem;"><source type="image/svg+xml" srcset="/assets/images/rownajac-w-dol/widely-available-540w.svg 540w" sizes="90vw"><img src="/assets/images/rownajac-w-dol/widely-available-540w.svg" width="540" height="300" style="aspect-ratio: 540 / 300" alt="" loading="lazy" decoding="async"></picture> <b lang="en">Widely available</b> (szeroko dostępny) – dany ficzer jest dostępny we wszystkich przeglądarkach od co najmniej 30 miesięcy (2.5 roku).</li>
</ul>
<p>Baseline ma dostarczać prostej odpowiedzi na pytanie, czy ficzer X można już używać w projektach. Niemniej nie mówi nic ponad to, w jakich przeglądarkach dany ficzer jest dostępny. Jeśli potrzebujemy większej liczby szczegółów, wciąż trzeba sięgnąć po dokładniejsze źródła, takie jak choćby wspomniane już Web Platform Tests. Co więcej, wskaźnik ten nie uwzględnia różnic w implementacji danego standardu w przeglądarkach. Jedynie wskazuje, czy jest on obsługiwany w <em>jakikolwiek</em> sposób. A to może też prowadzić do przekłamań, np. niektóre API są oznaczone jako mające ograniczoną dostępność, bo nie są dostępne w desktopowych przeglądarkach – mimo że niekoniecznie mają w nich sens (np. wspomniane już Web NFC).</p>
<p>Mimo swoich wad, Baseline już jest stałym elementem choćby dokumentacji MDN czy <a href="https://web.dev/blog/baseline-urlpattern?hl=en" rel="noreferrer noopener">Web.dev</a>. Osobiście mam co do niego mieszane uczucia, ale nie można mu odmówić jednego: robi dokładnie to, do czego go stworzono. A przez to już jest lepszy od wielu technologii sieciowych…</p>
]]></content>
		</entry>
	
		<entry>
			<title type="html">Ufam ci!</title>
			
				<author>
					<name>Comandeer</name>
				</author>
			
			<link href="https://blog.comandeer.pl/ufam-ci" rel="alternate" type="text/html"/>
			<published>2025-12-18T23:00:00.000Z</published>
			<updated>2025-12-18T23:00:00.000Z</updated>
			<id>https://blog.comandeer.pl/ufam-ci</id>
			
				<summary><![CDATA[HTML Sanitizer API to tylko jedna z metod obrony przed XSS w przeglądarkach. Drugą jest Trusted Types API.]]></summary>
			
			<content type="html"><![CDATA[<p><a href="https://blog.comandeer.pl/dezynfekcja">HTML Sanitizer API</a> jest dobrą i skuteczną&nbsp;odpowiedzią na problem ataków XSS. Ma jednak jedną, zasadniczą wadę: jego dodanie do już istniejących aplikacji wymaga ingerencji w kod. I to często w wielu miejscach. Na szczęście przeglądarki udostępniają jeszcze jedno API, które sprawdzi się o wiele lepiej w takiej sytuacji.</p>
<h2 id="problem-z-istniejącymi-aplikacjami"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#problem-z-istniejącymi-aplikacjami">Problem z istniejącymi aplikacjami</a></h2>
<p>Żeby zabezpieczyć aplikację przy pomocy HTML Sanitizer API, trzeba zadbać o to, żeby wszystkie miejsca, w których dodajemy HTML, korzystały z nowej <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML" rel="noreferrer noopener">metody <code>#setHTML()</code></a>. Innymi słowy: wszystkie wystąpienia <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML" rel="noreferrer noopener">własności <code>#innerHTML</code></a> trzeba podmienić. I choć na pierwszy rzut oka nie wydaje się to specjalnie skomplikowane, to z powodu złożoności aplikacji może to nastręczyć sporo problemów.</p>
<p>Pierwszym z nich jest zmiana zachowania aplikacji. Dotąd HTML był ustawiany bez dodatkowego czyszczenia. Teraz jest filtrowany. W zależności od aplikacji, może to być problemem. Weźmy takiego <a href="https://ckeditor.com/" rel="noreferrer noopener">CKEditora</a>. Jeśli nagle przestanie działać dodawanie obrazków w edytorze (bo HTML Sanitizer APi je wytnie), to on sam stanie się średnio użyteczny. Stąd proste podmienienie własności <code>#innerHTML</code> na metodę <code>#setHTML()</code> w takim wypadku nie zadziała.</p>
<p>Drugi z problemów związany jest z samym refactorem. Bo to prawie nigdy nie jest po prostu podmiana czegoś w kodzie. Często trzeba też zmienić choćby testy, które mogą zacząć padać po zmianie zachowania aplikacji. Często też trzeba sprawdzić, czemu testy <em>nie</em> zaczęły padać – by się upewnić, że na pewno wszystko dobrze testują.</p>
<p>Trzeci problem to zależności aplikacji. Nasz kod możemy w ten czy inny sposób zrefactoryzować. Ale z zależnościami nie jest już&nbsp;tak łatwo i najczęściej kończy się na czekaniu, aż dany projekt doda wsparcie dla nowego API. A na to se można poczekać – o ile dany projekt w ogóle będzie chciał taką zmianę wprowadzić.</p>
<p>I wreszcie: kompatybilność. Dopóki HTML Sanitizer API nie będzie miało sensownego wsparcia we wszystkich najważniejszych przeglądarkach, trzeba będzie zadbać o alternatywę. Najprawdopodobniej w postaci <a href="https://developer.mozilla.org/en-US/docs/Glossary/Polyfill" rel="noreferrer noopener">polyfilla</a>. A to dodatkowo niepotrzebnie skomplikuje kod i dołoży kolejną&nbsp;zależność do projektu.</p>
<p>Innymi słowy: wdrożenie HTML Sanitizer API może być sporym wyzwaniem w istniejących projektach.</p>
<h2 id="trusted-types-api"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#trusted-types-api">Trusted Types API</a></h2>
<p>Na szczęście istnieje inne API, które ma zdecydowanie lepsze wsparcie i które można wpiąć w już istniejące aplikacje przy minimalnej ingerencji. To API to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API" rel="noreferrer noopener"><i lang="en">Trusted Types API</i> (API Zaufanych Typów)</a>. Pozwala ono zabezpieczyć tzw. <i lang="en">injection sinks</i> (miejsca wstrzykiwania) przy pomocy polityk.</p>
<h3 id="miejsca-wstrzykiwania"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#miejsca-wstrzykiwania">Miejsca wstrzykiwania</a></h3>
<p>Miejsca wstrzykiwania to wszystkie te fragmenty kodu, w których do strony WWW jest dodawana treść lub może być wykonany skrypt JS. Najprostszym przykładem jest własność&nbsp;<code>#innerHTML</code>, przy pomocy której można wsadzić HTML do wybranego elementu. Są też jednak mniej oczywiste przykłady:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/text" rel="noreferrer noopener">własność <code>HTMLScriptElement#text</code></a> – służąca do zmiany zawartości elementu <code>script</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement/src" rel="noreferrer noopener">własność <code>HTMLScriptElement#src</code></a> – służąca do zmiany źródła elementu <code>script</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval" rel="noreferrer noopener">funkcja <code>eval()</code></a> – wykonująca przekazany jej kod JS,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString" rel="noreferrer noopener">metoda <code>DOMParser#parseFromString()</code></a> – parsująca przekazany kod HTML,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTMLUnsafe" rel="noreferrer noopener">metoda <code>setHTMLUnsafe()</code></a> – służąca do zmiany zawartości elementu z pominięciem czyszczenia przekazanego kodu.</li>
</ul>
<p>Takie przykłady można by mnożyć. MDN zawiera sporą <a href="https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API#injection_sink_interfaces" rel="noreferrer noopener">listę potencjalnych miejsc wstrzykiwania</a>.</p>
<h3 id="polityki"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#polityki">Polityki</a></h3>
<p>Trzonem Trusted Types API są tzw. polityki. To obiekty zawierające konfigurację opisują, w jaki sposób mają być zabezpieczone poszczególne miejsca wstrzykiwania. Każda polityka może składać się z trzech <a href="https://refactoring.guru/design-patterns/factory-method" rel="noreferrer noopener">metod-fabryk</a>:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy/createHTML" rel="noreferrer noopener"><code>#createHTML()</code></a> – służąca do tworzenia <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedHTML" rel="noreferrer noopener">obiektów <code>TrustedHTML</code></a>, które mogą być później używane wszędzie tam, gdzie trzeba przekazać kod HTML, np. własność <code>#innerHTML</code> lub metoda <code>#setHTMLUnsafe()</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy/createScript" rel="noreferrer noopener"><code>#createScript()</code></a> – służąca do tworzenia <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedScript" rel="noreferrer noopener">obiektów <code>TrustedScript</code></a>, które mogą być później używane tam, gdzie jest potrzebny kod JS, np. wewnątrz funkcji <code>eval()</code>,</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy/createScriptURL" rel="noreferrer noopener"><code>#createScriptURL()</code></a> – służąca do tworzenia <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedScriptURL" rel="noreferrer noopener">obiektów <code>TrustedScriptURL</code></a>, które mogą być później używane tam, gdzie potrzebny jest adres skryptu JS, np. we własności <code>HTMLScriptElement#src</code>.</li>
</ul>
<p>Żeby dodać nową politykę, trzeba skorzystać z <a href="https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory/createPolicy" rel="noreferrer noopener">metody <code>trustedTypes#createPolicy()</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> policy</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> trustedTypes.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createPolicy</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'my-policy'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( html );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScript</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">js</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeJS</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( js );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScriptURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( url );</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Tworzymy nową politykę o nazwie <code>my-policy</code> i zapisujemy ją do zmiennej <code>policy</code> (1). Ma ona wszystkie trzy metody – <code>#createHTML()</code> (2), <code>#createScript()</code> (3) oraz <code>#createScriptURL()</code> (4). Zwracają one przekazany im kod przepuszczony przez odpowiednie funkcje filtrujące (5, 6, 7 ). To, co te funkcje robią, zależy w zupełności od nas. W końcu każda aplikacja jest inna i może mieć inne wymagania odnośnie czyszczenia HTML-a czy JS-a. Na potrzeby przykładu stwórzmy zaślepki, które po prostu zwrócą przekazany im argument:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> html;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeJS</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">js</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> js;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">function</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> sanitizeURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">	return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> url;</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Żeby wykorzystać w praktyce naszą politykę, trzeba wywołać jej metody:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> div</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'div'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">div.innerHTML </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> policy.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'&lt;p&gt;Jestem zaufanym HTML-em&lt;/p&gt;'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">eval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( policy.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createScript</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'alert(1)'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> script</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">script.src </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> policy.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createScriptURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'data:text/javascript;charset=utf-8;base64,YWxlcnQoMSk='</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Tworzymy element <code>div</code> (1), a następnie wsadzamy do niego “zaufany” HTML, stworzony przy pomocy metody <code>policy#createHTML()</code> (2). Z kolei do funkcji <code>eval()</code> przekazujemy “zaufany” skrypt, stworzony przy pomocy metody <code>policy#createScript()</code> (3). Na koniec tworzymy element <code>script</code> (4) i ustawiamy jego własność <code>#src</code> na “zaufany” URL skryptu stworzony przy pomocy metody <code>policy#createScriptURL()</code> (5).</p>
<h3 id="wymuszanie-polityki"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#wymuszanie-polityki">Wymuszanie polityki</a></h3>
<p>Samo jednak dodanie polityki nie zabezpieczy aplikacji. Przeglądarka nie będzie miała żadnego problemu z tym, żeby ją ominąć i ustawić własność <code>#innerHTML</code> na dowolny tekst. Dlatego API dostarcza też mechanizm, który wymusza stosowanie polityki. Służy do tego <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/require-trusted-types-for" rel="noreferrer noopener">dyrektywa <code>require-trusted-types-for</code></a> w <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy" rel="noreferrer noopener">nagłówku HTTP <code>Content-Security-Policy</code></a>:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Content-Security-Policy: require-trusted-types-for 'script';</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Jak na razie jedyną wartością dla tej dyrektywy jest <code>'script'</code>, która pokrywa wszystkie trzy przypadki (kod HTML, kod JS i URL-e skryptów).</p>
<p>Jeśli wyślemy taki nagłówek HTTP, a następnie spróbujemy wstawić niezabezpieczony tekst do własności <code>#innerHTML</code>, powinniśmy otrzymać następujący błąd:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>This document requires 'TrustedHTML' assignment. The action has been blocked.</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Istnieje też <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/trusted-types" rel="noreferrer noopener">dyrektywa <code>trusted-types</code></a>, która kontroluje tworzenie polityk:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage"></span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span>Content-Security-Policy: require-trusted-types-for 'script';trusted-types my-policy;</span></span>
<span class="line"><span></span></span></code></pre>

			</div>
		</figure><p>Taki nagłówek wymusi używanie polityki, a równocześnie pozwoli stworzyć jedynie taką o nazwie <code>my-policy</code>.</p>
<h3 id="domyślna-polityka"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#domyślna-polityka">Domyślna polityka</a></h3>
<p>Ale zaraz, zaraz – czy to API nie miało być tym “mniej inwazyjnym”, które nie wymaga sporego refactoru istniejących aplikacji? Takie tworzenie polityk wydaje się wręcz jeszcze bardziej upierdliwe, niż prosta zamiana <code>#innerHTML</code> na <code>#setHTML()</code>. Na całe szczęście Trusted Types API ma asa w rękawie: domyślną politykę. Jeśli wymusimy stosowanie polityk przy pomocy dyrektywy <code>require-trusted-types-for</code>, a następnie stworzymy politykę o nazwie <code>default</code>, będzie ona automatycznie wykorzystywana w każdym miejscu wstrzykiwania:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">trustedTypes.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createPolicy</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'default'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, { </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createHTML</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">html</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'HTML'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 5</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> html; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScript</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">js</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'JS'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 6</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> js; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	},</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">	createScriptURL</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#E36209;--shiki-dark:#FFAB70">url</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">		console.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'URL'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 7</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">		return</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> url; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 4</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">	}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">} );</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Tworzymy nową politykę o nazwie <code>'default'</code> (1). Ponownie tworzymy metody-fabryki, w których zwracamy przekazany im kod (2, 3, 4). Wcześniej jednak logujemy w konsoli, jaki typ danych filtrujemy (5, 6, 7)</p>
<p>Wykorzystanie tej polityki jest całkowicie przezroczyste:</p>
<figure class="code" typeof="SoftwareSourceCode">
			<figcaption class="code__caption">
				<span class="code__title" property="programmingLanguage">JavaScript</span>
				
			</figcaption>
			<div class="code__code" translate="no" property="text">
				<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> div</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'div'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">div.innerHTML </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '&lt;p&gt;Jestem zaufanym HTML-em&lt;/p&gt;'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 1</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">eval</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'alert(1)'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ); </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">const</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> script</span><span style="color:#D73A49;--shiki-dark:#F97583"> =</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> document.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">createElement</span><span style="color:#24292E;--shiki-dark:#E1E4E8">( </span><span style="color:#032F62;--shiki-dark:#9ECBFF">'script'</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> );</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">script.src </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> 'data:text/javascript;charset=utf-8;base64,YWxlcnQoMSk='</span><span style="color:#24292E;--shiki-dark:#E1E4E8">; </span><span style="color:#6A737D;--shiki-dark:#6A737D">// 3</span></span>
<span class="line"></span></code></pre>

			</div>
		</figure><p>Nie trzeba się już odwoływać do polityki ani przy przypisywaniu wartości do własności <code>#innerHTML</code> (1), ani przy wywoływaniu funkcji <code>eval()</code> (2), ani przy ustawianiu źródła skryptu (3).</p>
<p>Jeśli odpalimy ten kod w przeglądarce, wówczas powinniśmy zobaczyć w konsoli logi potwierdzające, że polityka została zaaplikowana do każdego z trzech miejsc wstrzykiwania.</p>
<h3 id="kompatybilność"><a class="header-anchor" href="https://blog.comandeer.pl/ufam-ci#kompatybilność">Kompatybilność</a></h3>
<p><a href="https://w3c.github.io/trusted-types/dist/spec/" rel="noreferrer noopener">Specyfikacja Trusted Types API</a> jest już na dość zaawansowanym etapie standaryzacji. Choć wciąż może się zmieniać,  nie oczekiwałbym jakichś wielkich, wywrotowych zmian. Jak już jakieś nastąpią, to raczej kosmetyczne. Natomiast <a href="https://caniuse.com/trusted-types" rel="noreferrer noopener">wsparcie</a> jest już od dłuższego czasu zarówno w Chrome, jak i w Safari. W Firefoksie wciąż jest eksperymentalne za flagą.</p>
<p>Myślę, że dopóki wsparcie nie będzie we wszystkich najważniejszych przeglądarkach, najlepiej się ograniczyć do tworzenia domyślnej polityki i wymuszać ją przy pomocy dyrektywy <code>require-trusted-types-for</code>. Dzięki temu całość będzie jak najbardziej transparentna dla przeglądarek, które wciąż nie wspierają tego API. Natomiast cała reszta dostanie lepsze zabezpieczenie przed atakami XSS.</p>
<p>I w końcu będzie można zacząć&nbsp;ufać własności <code>#innerHTML</code>!</p>
]]></content>
		</entry>
	
</feed>
