A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="en">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>A microdata enhanced HTML Webcomponent for Leaflet (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Is that even respected? Retrospectively? What a shAItshow…
  28. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  29. <meta name="robots" content="noai, noimageai">
  30. <!-- Documented, feel free to shoot an email. -->
  31. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  32. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  36. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  37. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  38. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  39. <script>
  40. function toggleTheme(themeName) {
  41. document.documentElement.classList.toggle(
  42. 'forced-dark',
  43. themeName === 'dark'
  44. )
  45. document.documentElement.classList.toggle(
  46. 'forced-light',
  47. themeName === 'light'
  48. )
  49. }
  50. const selectedTheme = localStorage.getItem('theme')
  51. if (selectedTheme !== 'undefined') {
  52. toggleTheme(selectedTheme)
  53. }
  54. </script>
  55. <meta name="robots" content="noindex, nofollow">
  56. <meta content="origin-when-cross-origin" name="referrer">
  57. <!-- Canonical URL for SEO purposes -->
  58. <link rel="canonical" href="https://blog.k-nut.eu/leaflet-microdata-html-webcomponent">
  59. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  60. <article>
  61. <header>
  62. <h1>A microdata enhanced HTML Webcomponent for Leaflet</h1>
  63. </header>
  64. <nav>
  65. <p class="center">
  66. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  67. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  68. </svg> Accueil</a> •
  69. <a href="https://blog.k-nut.eu/leaflet-microdata-html-webcomponent" title="Lien vers le contenu original">Source originale</a>
  70. <br>
  71. Mis en cache le 2024-03-25
  72. </p>
  73. </nav>
  74. <hr>
  75. <p>The people in my RSS reader have been talking some more about Web Components in the
  76. past couple of months. Jim Nielsen had <a href="https://blog.jim-nielsen.com/2023/html-web-components/">a post</a>
  77. referring to Jeremy Keith coining the term <a href="https://adactio.com/journal/20618">HTML Web Components</a>
  78. for a special kind of Web Components. The idea essentially is that you have regular HTML markup that is wrapped
  79. by a custom Web Component which enhances the user experience.</p>
  80. <p>I thought that this was quite an interesting idea and thought about situations in which
  81. it could be applied. Working with open data quite a bit, we often find ourselves in
  82. situations where we build maps. Additionally, the German open data scene has been lobbying for
  83. more linked open data in the past couple of months. I think I came up with an example which
  84. combines these three things - HTML Web Components, maps and Linked Data (or a flavor
  85. thereof) quite nicely.</p>
  86. <p>As an example, assume that we want to render schools on a map. The data in
  87. our example is taken from our <a href="https://jedeschule.codefor.de/docs">jedeschule.de API</a>
  88. which has the goal of making all primary and secondary schools in Germany
  89. searchable and queryable.</p>
  90. <p>With the approach I’m proposing, we can author markup that looks like this:</p>
  91. <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;leaflet-map&gt;</span>
  92. <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"map"</span><span class="nt">&gt;&lt;/div&gt;</span>
  93. <span class="nt">&lt;ul</span> <span class="na">class=</span><span class="s">"locations"</span><span class="nt">&gt;</span>
  94. <span class="nt">&lt;li</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/School"</span> <span class="na">data-fresh-key=</span><span class="s">"NW-153710"</span><span class="nt">&gt;</span>
  95. <span class="nt">&lt;h2</span> <span class="na">itemprop=</span><span class="s">"name"</span><span class="nt">&gt;</span>Dahlingschule, Städt. Förderschule im integr. Verbund, FSP Lernen u. Emot. und soziale Entwicklung,-Primarstufe u. SekI<span class="nt">&lt;/h2&gt;</span>
  96. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"address"</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/PostalAddress"</span><span class="nt">&gt;</span>
  97. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"streetAddress"</span><span class="nt">&gt;</span>Dahlingstr. 40<span class="nt">&lt;/div&gt;</span>
  98. <span class="nt">&lt;span</span> <span class="na">itemprop=</span><span class="s">"postalCode"</span><span class="nt">&gt;</span>47229<span class="nt">&lt;/span&gt;</span>
  99. <span class="nt">&lt;span</span> <span class="na">itemprop=</span><span class="s">"addressLocality"</span><span class="nt">&gt;</span>Duisburg<span class="nt">&lt;/span&gt;</span>
  100. <span class="nt">&lt;/div&gt;</span>
  101. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"geo"</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/GeoCoordinates"</span><span class="nt">&gt;</span>
  102. <span class="nt">&lt;meta</span> <span class="na">itemprop=</span><span class="s">"latitude"</span> <span class="na">content=</span><span class="s">"51.38331874331818"</span><span class="nt">/&gt;</span>
  103. <span class="nt">&lt;meta</span> <span class="na">itemprop=</span><span class="s">"longitude"</span> <span class="na">content=</span><span class="s">"6.700606101666003"</span><span class="nt">/&gt;</span>
  104. <span class="nt">&lt;/div&gt;</span>
  105. <span class="nt">&lt;/li&gt;</span>
  106. <span class="nt">&lt;li</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/School"</span> <span class="na">data-fresh-key=</span><span class="s">"NW-166480"</span><span class="nt">&gt;</span>
  107. <span class="nt">&lt;h2</span> <span class="na">itemprop=</span><span class="s">"name"</span><span class="nt">&gt;</span>Montessori-Gymnasium Städt. Gymnasium für Jungen und Mädchen<span class="nt">&lt;/h2&gt;</span>
  108. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"address"</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/PostalAddress"</span><span class="nt">&gt;</span>
  109. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"streetAddress"</span><span class="nt">&gt;</span>Rochusstr. 145<span class="nt">&lt;/div&gt;</span>
  110. <span class="nt">&lt;span</span> <span class="na">itemprop=</span><span class="s">"postalCode"</span><span class="nt">&gt;</span>50827<span class="nt">&lt;/span&gt;</span>
  111. <span class="nt">&lt;span</span> <span class="na">itemprop=</span><span class="s">"addressLocality"</span><span class="nt">&gt;</span>Köln<span class="nt">&lt;/span&gt;</span>
  112. <span class="nt">&lt;/div&gt;</span>
  113. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"geo"</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/GeoCoordinates"</span><span class="nt">&gt;</span>
  114. <span class="nt">&lt;meta</span> <span class="na">itemprop=</span><span class="s">"latitude"</span> <span class="na">content=</span><span class="s">"50.963204818343755"</span><span class="nt">/&gt;</span>
  115. <span class="nt">&lt;meta</span> <span class="na">itemprop=</span><span class="s">"longitude"</span> <span class="na">content=</span><span class="s">"6.904840537750957"</span><span class="nt">/&gt;</span>
  116. <span class="nt">&lt;/div&gt;</span>
  117. <span class="nt">&lt;/li&gt;</span>
  118. <span class="nt">&lt;li</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/School"</span> <span class="na">data-fresh-key=</span><span class="s">"NW-167782"</span><span class="nt">&gt;</span>
  119. <span class="nt">&lt;h2</span> <span class="na">itemprop=</span><span class="s">"name"</span><span class="nt">&gt;</span>Städt. Grillo-Gymnasium<span class="nt">&lt;/h2&gt;</span>
  120. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"address"</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/PostalAddress"</span><span class="nt">&gt;</span>
  121. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"streetAddress"</span><span class="nt">&gt;</span>Hauptstr. 60<span class="nt">&lt;/div&gt;</span>
  122. <span class="nt">&lt;span</span> <span class="na">itemprop=</span><span class="s">"postalCode"</span><span class="nt">&gt;</span>45879<span class="nt">&lt;/span&gt;</span>
  123. <span class="nt">&lt;span</span> <span class="na">itemprop=</span><span class="s">"addressLocality"</span><span class="nt">&gt;</span>Gelsenkirchen<span class="nt">&lt;/span&gt;</span>
  124. <span class="nt">&lt;/div&gt;</span>
  125. <span class="nt">&lt;div</span> <span class="na">itemprop=</span><span class="s">"geo"</span> <span class="na">itemscope</span> <span class="na">itemtype=</span><span class="s">"https://schema.org/GeoCoordinates"</span><span class="nt">&gt;</span>
  126. <span class="nt">&lt;meta</span> <span class="na">itemprop=</span><span class="s">"latitude"</span> <span class="na">content=</span><span class="s">"51.5130837882258"</span><span class="nt">/&gt;</span>
  127. <span class="nt">&lt;meta</span> <span class="na">itemprop=</span><span class="s">"longitude"</span> <span class="na">content=</span><span class="s">"7.099798427442939"</span><span class="nt">/&gt;</span>
  128. <span class="nt">&lt;/div&gt;</span>
  129. <span class="nt">&lt;/li&gt;</span>
  130. <span class="nt">&lt;/ul&gt;</span>
  131. <span class="nt">&lt;/leaflet-map&gt;</span>
  132. <span class="nt">&lt;style&gt;</span>
  133. <span class="nf">#map</span> <span class="p">{</span>
  134. <span class="nl">width</span><span class="p">:</span> <span class="m">800px</span><span class="p">;</span>
  135. <span class="nl">height</span><span class="p">:</span> <span class="nb">auto</span><span class="p">;</span>
  136. <span class="py">aspect-ratio</span><span class="p">:</span> <span class="m">16</span><span class="p">/</span><span class="m">9</span><span class="p">;</span>
  137. <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("/map.png")</span><span class="p">;</span>
  138. <span class="nl">background-size</span><span class="p">:</span> <span class="n">contain</span><span class="p">;</span>
  139. <span class="p">}</span>
  140. <span class="nt">&lt;/style&gt;</span>
  141. </code></pre></div></div>
  142. <p>In this form, this isn’t interactive yet but it can already be easily consumed by both humans
  143. and computers. Humans will see a static map (I simply took a screenshot and set is as a
  144. background image for the <code class="highlighter-rouge">#map</code> node) and a list of schools with their addresses.
  145. Computers will be able to also parse the schema.org annotations to extract structured data out of the list.</p>
  146. <p>Since the data is semi-structured and machine readable, we can now also consume it from
  147. a Web Component to add interactivity with <a href="https://leafletjs.com">Leaflet</a>. The code to do so
  148. looks like this:</p>
  149. <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">LeafletMap</span> <span class="kd">extends</span> <span class="nx">HTMLElement</span> <span class="p">{</span>
  150. <span class="nx">connectedCallback</span><span class="p">()</span> <span class="p">{</span>
  151. <span class="kd">const</span> <span class="nx">mapElement</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s2">"#map"</span><span class="p">);</span>
  152. <span class="nx">mapElement</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundImage</span> <span class="o">=</span> <span class="s2">"none"</span><span class="p">;</span>
  153. <span class="kd">const</span> <span class="nx">schools</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span>
  154. <span class="s1">'[itemType="https://schema.org/School"]'</span><span class="p">,</span>
  155. <span class="p">);</span>
  156. <span class="kd">var</span> <span class="nx">map</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">mapElement</span><span class="p">).</span><span class="nx">setView</span><span class="p">([</span><span class="mf">51.505</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.09</span><span class="p">],</span> <span class="mi">13</span><span class="p">);</span>
  157. <span class="nx">L</span><span class="p">.</span><span class="nx">tileLayer</span><span class="p">(</span><span class="s2">"https://tile.openstreetmap.org/{z}/{x}/{y}.png"</span><span class="p">,</span> <span class="p">{</span>
  158. <span class="na">attribution</span><span class="p">:</span>
  159. <span class="s1">'&amp;copy; &lt;a href="https://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt; contributors'</span><span class="p">,</span>
  160. <span class="p">}).</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
  161. <span class="kd">const</span> <span class="nx">markers</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">L</span><span class="p">.</span><span class="nx">featureGroup</span><span class="p">();</span>
  162. <span class="nx">schools</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">school</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  163. <span class="kd">const</span> <span class="nx">latitude</span> <span class="o">=</span> <span class="nx">school</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'[itemProp="latitude"]'</span><span class="p">).</span><span class="nx">content</span><span class="p">;</span>
  164. <span class="kd">const</span> <span class="nx">longitude</span> <span class="o">=</span> <span class="nx">school</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'[itemProp="longitude"]'</span><span class="p">).</span><span class="nx">content</span><span class="p">;</span>
  165. <span class="kd">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">school</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'[itemProp="name"]'</span><span class="p">).</span><span class="nx">textContent</span><span class="p">;</span>
  166. <span class="kd">const</span> <span class="nx">address</span> <span class="o">=</span> <span class="nx">school</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'[itemProp="address"]'</span><span class="p">);</span>
  167. <span class="kd">const</span> <span class="nx">h2</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"h2"</span><span class="p">);</span>
  168. <span class="nx">h2</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
  169. <span class="kd">const</span> <span class="nx">popup</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"div"</span><span class="p">);</span>
  170. <span class="nx">popup</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="s2">"popup"</span><span class="p">);</span>
  171. <span class="nx">popup</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">h2</span><span class="p">);</span>
  172. <span class="nx">popup</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">address</span><span class="p">.</span><span class="nx">cloneNode</span><span class="p">(</span><span class="kc">true</span><span class="p">));</span>
  173. <span class="kd">const</span> <span class="nx">marker</span> <span class="o">=</span> <span class="nx">L</span><span class="p">.</span><span class="nx">marker</span><span class="p">([</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">]).</span><span class="nx">bindPopup</span><span class="p">(</span><span class="nx">popup</span><span class="p">);</span>
  174. <span class="nx">marker</span><span class="p">.</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">markers</span><span class="p">);</span>
  175. <span class="p">});</span>
  176. <span class="nx">markers</span><span class="p">.</span><span class="nx">addTo</span><span class="p">(</span><span class="nx">map</span><span class="p">);</span>
  177. <span class="nx">map</span><span class="p">.</span><span class="nx">fitBounds</span><span class="p">(</span><span class="nx">markers</span><span class="p">.</span><span class="nx">getBounds</span><span class="p">());</span>
  178. <span class="p">}</span>
  179. <span class="p">}</span>
  180. <span class="nb">window</span><span class="p">.</span><span class="nx">customElements</span><span class="p">.</span><span class="nx">define</span><span class="p">(</span><span class="s2">"leaflet-map"</span><span class="p">,</span> <span class="nx">LeafletMap</span><span class="p">);</span>
  181. </code></pre></div></div>
  182. <p>I quite like the way that this reads. The <code class="highlighter-rouge">itemProp</code>s make for very nice selectors and actually
  183. allow developers to completely change the structure of the HTML. As long as the annotations
  184. are kept, the Web Component will be able to still extract the locations and to load them into
  185. leaflet.</p>
  186. <p>You can try this on CodePen:</p>
  187. <p>This of course is nowhere from production ready but shall only serve as a proof of concept.
  188. In a more advanced version, we wouldn’t limit our initial query selector to <code class="highlighter-rouge">[itemType="https://schema.org/School]</code>
  189. but would probably want to find a way to query for everything that is a child of <code class="highlighter-rouge">https://schema.org/Place</code>.
  190. We would also need to adapt the static image if we wanted to change the underlying data. If we have server
  191. side rendering at our disposal, we could use something like the <a href="https://docs.mapbox.com/api/maps/static-images/">Mapbox Static Images
  192. API</a> to dynamically create the fallback
  193. images for clients that do not support JavaScript or Web Components.</p>
  194. <p>All in all, this feels like a nice approach to me though. It provides content
  195. that can be consumed by computers and humans with up to date or legacy (or privacy concious)
  196. devices easily. Using the <code class="highlighter-rouge">itemProp</code>s for query selectors also makes for a nice
  197. authoring experience with good separation of concerns.</p>
  198. <p>What do you think? Let me know on <a href="https://berlin.social/@knut">mastodon</a>.</p>
  199. </article>
  200. <hr>
  201. <footer>
  202. <p>
  203. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  204. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  205. </svg> Accueil</a> •
  206. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  207. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  208. </svg> Suivre</a> •
  209. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  210. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  211. </svg> Pro</a> •
  212. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  213. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  214. </svg> Email</a> •
  215. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  216. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  217. </svg> Légal</abbr>
  218. </p>
  219. <template id="theme-selector">
  220. <form>
  221. <fieldset>
  222. <legend><svg class="icon icon-brightness-contrast">
  223. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  224. </svg> Thème</legend>
  225. <label>
  226. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  227. </label>
  228. <label>
  229. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  230. </label>
  231. <label>
  232. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  233. </label>
  234. </fieldset>
  235. </form>
  236. </template>
  237. </footer>
  238. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  239. <script>
  240. function loadThemeForm(templateName) {
  241. const themeSelectorTemplate = document.querySelector(templateName)
  242. const form = themeSelectorTemplate.content.firstElementChild
  243. themeSelectorTemplate.replaceWith(form)
  244. form.addEventListener('change', (e) => {
  245. const chosenColorScheme = e.target.value
  246. localStorage.setItem('theme', chosenColorScheme)
  247. toggleTheme(chosenColorScheme)
  248. })
  249. const selectedTheme = localStorage.getItem('theme')
  250. if (selectedTheme && selectedTheme !== 'undefined') {
  251. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  252. }
  253. }
  254. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  255. window.addEventListener('load', () => {
  256. let hasDarkRules = false
  257. for (const styleSheet of Array.from(document.styleSheets)) {
  258. let mediaRules = []
  259. for (const cssRule of styleSheet.cssRules) {
  260. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  261. continue
  262. }
  263. // WARNING: Safari does not have/supports `conditionText`.
  264. if (cssRule.conditionText) {
  265. if (cssRule.conditionText !== prefersColorSchemeDark) {
  266. continue
  267. }
  268. } else {
  269. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  270. continue
  271. }
  272. }
  273. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  274. }
  275. // WARNING: do not try to insert a Rule to a styleSheet you are
  276. // currently iterating on, otherwise the browser will be stuck
  277. // in a infinite loop…
  278. for (const mediaRule of mediaRules) {
  279. styleSheet.insertRule(mediaRule.cssText)
  280. hasDarkRules = true
  281. }
  282. }
  283. if (hasDarkRules) {
  284. loadThemeForm('#theme-selector')
  285. }
  286. })
  287. </script>
  288. </body>
  289. </html>