Repository with sources and generator of https://larlet.fr/david/ https://larlet.fr/david/
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 25KB

8 maanden geleden
3 weken geleden
8 maanden geleden
8 maanden geleden
8 maanden geleden
8 maanden geleden
8 maanden geleden
8 maanden geleden
8 maanden geleden
8 maanden geleden
8 maanden geleden
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <!DOCTYPE html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  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>
  13. GPX Viewer
  14. — David Larlet</title>
  15. <script>
  16. function toggleTheme(themeName) {
  17. document.documentElement.classList.toggle(
  18. 'forced-dark',
  19. themeName === 'dark'
  20. )
  21. document.documentElement.classList.toggle(
  22. 'forced-light',
  23. themeName === 'light'
  24. )
  25. }
  26. const selectedTheme = localStorage.getItem('theme')
  27. if (selectedTheme !== 'undefined') {
  28. toggleTheme(selectedTheme)
  29. }
  30. </script>
  31. <!-- Documented, feel free to shoot an email. -->
  32. <link rel="stylesheet" href="/static/david/css/style_2024-03-09.css">
  33. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  34. <link rel="preload"
  35. href="/static/david/css/fonts/century_supra_ot_a_regular.woff2"
  36. as="font"
  37. type="font/woff2"
  38. media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  39. crossorigin>
  40. <link rel="preload"
  41. href="/static/david/css/fonts/century_supra_ot_a_bold.woff2"
  42. as="font"
  43. type="font/woff2"
  44. media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  45. crossorigin>
  46. <link rel="preload"
  47. href="/static/david/css/fonts/century_supra_ot_a_italic.woff2"
  48. as="font"
  49. type="font/woff2"
  50. media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  51. crossorigin>
  52. <link rel="preload"
  53. href="/static/david/css/fonts/century_supra_ot_b_regular.woff2"
  54. as="font"
  55. type="font/woff2"
  56. media="(prefers-color-scheme: dark)"
  57. crossorigin>
  58. <link rel="preload"
  59. href="/static/david/css/fonts/century_supra_ot_b_bold.woff2"
  60. as="font"
  61. type="font/woff2"
  62. media="(prefers-color-scheme: dark)"
  63. crossorigin>
  64. <link rel="preload"
  65. href="/static/david/css/fonts/century_supra_ot_b_italic.woff2"
  66. as="font"
  67. type="font/woff2"
  68. media="(prefers-color-scheme: dark)"
  69. crossorigin>
  70. <meta name="description" content="Suite de mes expérimentations de la veille à la fois en cartographie et en web components. Je me suis demandé s’il était possible de faire un composant qui ne dépende pas de uMap pour afficher une trace GPX. Le fait d’avoir passé une heure à trouver la bonne CSP pour arriver à afficher une carte m’a d’une certaine manière motivé (c’était une journée galère).">
  71. <!-- That good ol' feed, subscribe :). -->
  72. <link rel="alternate"
  73. type="application/atom+xml"
  74. title="Feed"
  75. href="/david/log/">
  76. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  77. <link rel="apple-touch-icon"
  78. sizes="180x180"
  79. href="/static/david/icons2/apple-touch-icon.png">
  80. <link rel="icon"
  81. type="image/png"
  82. sizes="32x32"
  83. href="/static/david/icons2/favicon-32x32.png">
  84. <link rel="icon"
  85. type="image/png"
  86. sizes="16x16"
  87. href="/static/david/icons2/favicon-16x16.png">
  88. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  89. <link rel="mask-icon"
  90. href="/static/david/icons2/safari-pinned-tab.svg"
  91. color="#07486c">
  92. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  93. <meta name="msapplication-TileColor" content="#f7f7f7">
  94. <meta name="msapplication-config"
  95. content="/static/david/icons2/browserconfig.xml">
  96. <meta name="theme-color"
  97. content="#f7f7f7"
  98. media="(prefers-color-scheme: light)">
  99. <meta name="theme-color"
  100. content="#272727"
  101. media="(prefers-color-scheme: dark)">
  102. <!-- Is that even respected? Retrospectively? What a shAItshow…
  103. https://neil-clarke.com/block-the-bots-that-feed-ai-models-by-scraping-your-website/ -->
  104. <meta name="robots" content="noai, noimageai">
  105. <!-- To get attribution when linking on mastodon. -->
  106. <meta name="fediverse:creator" content="@david@larlet.fr">
  107. <style type="text/css">
  108. .tippy-content {
  109. min-width: 280px;
  110. padding: .5rem;
  111. font-size: calc(var(--fluid-0) * 0.8);
  112. font-family: var(--labor-font);
  113. letter-spacing: initial;
  114. text-align: left;
  115. }
  116. .tippy-content h3 {
  117. margin-top: 0;
  118. }
  119. .tippy-content h3 img {
  120. max-width: 2rem;
  121. max-height: 2rem;
  122. display: inline-block;
  123. }
  124. .tippy-content .tippy-links {
  125. display: flex;
  126. justify-content: space-around;
  127. }
  128. .tippy-content a {
  129. padding: .4rem;
  130. color: #F06048;
  131. }
  132. </style>
  133. <body data-instant-intensity="viewport-all">
  134. <article>
  135. <header>
  136. <hgroup>
  137. <h1>GPX Viewer</h1>
  138. <p>Le <time datetime="2024-03-26">26 mars 2024</time></p>
  139. </hgroup>
  140. </header>
  141. <nav>
  142. <p>
  143. <a rel="prev"
  144. href="/david/2024/03/25/"
  145. title="Publication précédente : Inclusion">← Précédent</a> •
  146. <a href="/david/" title="Aller à l’accueil" rel="up">Accueil</a>
  147. <a href="/david/recherche/"
  148. title="Aller à la page de recherche"
  149. rel="search" data-no-instant>Recherche</a>
  150. • <a rel="next"
  151. href="/david/2024/03/28/"
  152. title="Publication suivante : Collectif">Suivant →</a>
  153. </p>
  154. </nav>
  155. <p><a href="/david/2024/03/25/" title="Inclusion">Suite de mes expérimentations</a> de la veille à la fois en cartographie et en <em>web components</em>. Je me suis demandé s’il était possible de faire un composant qui ne dépende pas de uMap pour afficher une trace GPX. Le fait d’avoir passé une heure à trouver la bonne <a data-link-domain="MDN" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">CSP</a> pour arriver à afficher une carte m’a d’une certaine manière motivé (c’était une journée&nbsp;galère).</p>
  156. <p>Il se trouve qu’il existe <a data-link-domain="github.com" href="https://github.com/mpetazzoni/leaflet-gpx">leaflet-gpx</a> qui fait exactement ce que je voulais. Il ne me restait «&nbsp;plus qu’à&nbsp;» en faire un composant web, ce que j’avais déjà <a data-link-domain="gitlab.com" href="https://gitlab.com/umap-project/leaflet-webcomponents/">fait par ailleurs</a>&nbsp;:</p>
  157. <gpx-viewer data-height="500px" data-width="800px">
  158. <p>
  159. Vous devriez voir s’afficher une carte de mon « Grand Tour de la forêt de Ouareau »,
  160. qui contient <a data-gpx href="/static/david/2024/grand_tour_de_la_foret_de_ouareau.gpx">cette trace GPX</a>,
  161. centrée sur <span data-latitude>46.2117</span>, <span data-longitude>-73.9335</span>
  162. avec un zoom de <span data-zoom>12</span>.
  163. Il est probable que cela ne s’exécute pas dans un agrégateur par exemple.
  164. </p>
  165. </gpx-viewer>
  166. <script type="module">
  167. import * as L from '/static/david/2024/leaflet.1.9.4/leaflet.1.9.4.js'
  168. window.L = L // Hack for leaflet-gpx, youpi.
  169. </script>
  170. <script type="module" nonce="oembed-web-component">
  171. class GPXViewer extends HTMLElement {
  172. static tagName = 'gpx-viewer'
  173. static register(tagName, registry) {
  174. if(!registry && ('customElements' in globalThis)) {
  175. registry = globalThis.customElements
  176. }
  177. registry?.define(tagName || this.tagName, this)
  178. }
  179. #attachCSS(path) {
  180. const linkElement = document.createElement('link')
  181. linkElement.setAttribute('rel', 'stylesheet')
  182. linkElement.setAttribute('href', path)
  183. this.shadowRoot.appendChild(linkElement)
  184. }
  185. #computeDimensions(mapContainer) {
  186. // There has to be a better way but I feel lazy tonight.
  187. let height = this.dataset.height
  188. let width = this.dataset.width
  189. // Size is in px so we strip these chars and convert to int.
  190. const heightValue = Number(height.slice(0, -2))
  191. const widthValue = Number(width.slice(0, -2))
  192. const mediaQueryMiddle = window.matchMedia(`(max-width: ${widthValue}px)`)
  193. const mediaQuerySmall = window.matchMedia(`(max-width: ${widthValue / 2}px)`)
  194. if (mediaQueryMiddle.matches) {
  195. height = `${heightValue / 2}px`
  196. width = `${widthValue / 2}px`
  197. }
  198. if (mediaQuerySmall.matches) {
  199. height = `${heightValue / 3}px`
  200. width = `${widthValue / 3}px`
  201. }
  202. mapContainer.style.height = height
  203. mapContainer.style.width = width
  204. }
  205. #createMapContainer() {
  206. const mapContainer = document.createElement('div')
  207. this.#computeDimensions(mapContainer)
  208. this.shadowRoot.appendChild(mapContainer)
  209. return mapContainer
  210. }
  211. #createMap(mapContainer) {
  212. const map = L.map(mapContainer).setView(
  213. [
  214. this.querySelector('[data-latitude]').textContent,
  215. this.querySelector('[data-longitude]').textContent,
  216. ],
  217. this.querySelector('[data-zoom]').textContent
  218. )
  219. L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
  220. attribution:
  221. '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
  222. }).addTo(map)
  223. return map
  224. }
  225. #attachGPX(map) {
  226. const gpxUrl = this.querySelector('[data-gpx]').href
  227. new GPX(gpxUrl, {
  228. async: true,
  229. marker_options: {
  230. startIconUrl: '/static/david/2024/leaflet-gpx.1.7.0-custom/pin-icon-start.png',
  231. endIconUrl: '/static/david/2024/leaflet-gpx.1.7.0-custom/pin-icon-end.png',
  232. shadowUrl: '/static/david/2024/leaflet-gpx.1.7.0-custom/pin-shadow.png'
  233. }
  234. }).on('loaded', (e) => {
  235. map.fitBounds(e.target.getBounds())
  236. }).addTo(map)
  237. }
  238. constructor() {
  239. super()
  240. this.attachShadow({ mode: 'open' })
  241. }
  242. async connectedCallback() {
  243. this.#attachCSS('/static/david/2024/leaflet.1.9.4/leaflet.css')
  244. const mapContainer = this.#createMapContainer()
  245. const map = this.#createMap(mapContainer)
  246. this.#attachGPX(map)
  247. }
  248. }
  249. import GPX from '/static/david/2024/leaflet-gpx.1.7.0-custom/gpx.1.7.0-custom.js'
  250. GPXViewer.register()
  251. </script>
  252. <p>Alors en fait, comme rien n’est jamais simple en JS, il a fallu que j’adapte le plugin qui n’était pas compatible avec les modules JS et que je fasse des galipettes pour que ça finisse par tomber en marche&nbsp;🤸. Cet écosystème est merveilleux (quel métier&#8239;!). J’ai quand même <a data-link-domain="github.com" href="https://github.com/mpetazzoni/leaflet-gpx/issues/153">remonté le problème</a>.</p>
  253. <p>Au passage, inspiré par <a data-link-domain="blog.k-nut.eu" href="https://blog.k-nut.eu/leaflet-microdata-html-webcomponent" hreflang="en"
  254. title="Consultation de l’article (anglais)">le travail de Knut Hühne</a>
  255. <a href="/david/cache/2024/65fba9cd025cd2403f932cb2c928cf14/" hreflang="en"
  256. data-tippy data-description="An example of using schema.org microdata to build a HTML Webcomponent for Leaflet"
  257. data-source="https://blog.k-nut.eu/leaflet-microdata-html-webcomponent"
  258. data-date="2024-03-25"
  259. data-favicon="https://blog.k-nut.eu/favicon-32x32.png"
  260. data-domain="blog.k-nut.eu"
  261. ><svg xmlns="http://www.w3.org/2000/svg"
  262. width="24" height="24" viewBox="0 0 24 24" fill="none"
  263. stroke="currentColor" stroke-width="2" stroke-linecap="square"
  264. stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle>
  265. <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
  266. <line x1="12" y1="17" x2="12.01" y2="17"></line>
  267. </svg>
  268. <span class="sr-only">[archive]</span></a> sur la sémantique des données d’une carte, je me suis demandé si j’allais définir un <a data-link-domain="schema.org" href="https://schema.org/itinerary">itinerary</a> pour un <a data-link-domain="schema.org" href="https://schema.org/Trip">Trip</a> avec des <a data-link-domain="schema.org" href="https://schema.org/GeoCoordinates">GeoCoordinates</a> mais ça m’a fait tirer un fil beaucoup trop long. Je garde l’idée sous le coude <a data-link-domain="umap-project.gitlab.io" href="https://umap-project.gitlab.io/leaflet-webcomponents/">pour mes propres recherches</a> dans le domaine cela&nbsp;dit.</p>
  269. <p>Un composant de plus, je ne suis pas très satisfait de celui-ci car j’avais l’espoir d’arriver à quelque chose de facilement réutilisable. J’aurais au moins commencé à mettre les doigts dans le XML du format GPX. Il est possible de définir plusieurs segments au sein d’une même trace. Il peut y avoir pas mal de métadonnées, notamment de mise en forme de la trace mais aussi de caractéristiques (biologiques, physiques, etc).</p>
  270. <a href="#hr-138" title="Lien vers cette section de la page"><hr id="hr-138" /></a>
  271. <blockquote lang="en">
  272. <p>The fact is that we can’t rely on any single website to hold the whole world’s knowledge, because it can be corrupted sooner or later. The only solution is a distributed architecture, with many smaller websites connecting with each other and sharing information. This is where ActivityPub comes in, the protocol used by Mastodon, Lemmy, Peertube and many other federated social media&nbsp;projects.</p>
  273. <p>I have worked on Lemmy for the past four years, bringing it from a prototype to a fully functional Reddit alternative. I wrote the entire federation code and became very familiar with the protocol. <mark>I realized that the same technology easily be used to create a federated encyclopedia.</mark> As no one else took up such a project, I finally decided to do it on my own and create Ibis. Thanks to my previous experience with the tech stack and the ActivityPub library I created, I was able to complete a proof of concept in a relatively short time of four&nbsp;months.</p>
  274. <p><cite><em><a data-link-domain="ibis.wiki" href="https://ibis.wiki/article/Announcing_Ibis,_the_federated_Wikipedia_Alternative@ibis.wiki" hreflang="en"
  275. title="Consultation de l’article (anglais)">Announcing Ibis, the federated Wikipedia Alternative</a>
  276. <a href="/david/cache/2024/691120ebac09f68413501b7f5daa2db7/" hreflang="en"
  277. data-tippy data-description=""
  278. data-source="https://ibis.wiki/article/Announcing_Ibis,_the_federated_Wikipedia_Alternative@ibis.wiki"
  279. data-date="2024-03-26"
  280. data-favicon=""
  281. data-domain="ibis.wiki"
  282. ><svg xmlns="http://www.w3.org/2000/svg"
  283. width="24" height="24" viewBox="0 0 24 24" fill="none"
  284. stroke="currentColor" stroke-width="2" stroke-linecap="square"
  285. stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle>
  286. <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
  287. <line x1="12" y1="17" x2="12.01" y2="17"></line>
  288. </svg>
  289. <span class="sr-only">[archive]</span></a></em></cite></p>
  290. </blockquote>
  291. <p>Wikipedia décentralisé, je dis oui&#8239;! Merci <a data-link-domain="alexsirac.com" href="https://alexsirac.com/activitypub-powered-wikis/" hreflang="fr"
  292. title="Consultation de l’article">Alex</a>
  293. <a href="/david/cache/2024/55c19feff784a41d2527b5f1589d931d/" hreflang="fr"
  294. data-tippy data-description=""
  295. data-source="https://alexsirac.com/activitypub-powered-wikis/"
  296. data-date="2024-03-26"
  297. data-favicon="https://alexsirac.com/wp-content/uploads/2023/01/cropped-cropped-portraitplante-32x32.webp"
  298. data-domain="alexsirac.com"
  299. ><svg xmlns="http://www.w3.org/2000/svg"
  300. width="24" height="24" viewBox="0 0 24 24" fill="none"
  301. stroke="currentColor" stroke-width="2" stroke-linecap="square"
  302. stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle>
  303. <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
  304. <line x1="12" y1="17" x2="12.01" y2="17"></line>
  305. </svg>
  306. <span class="sr-only">[archive]</span></a>.</p>
  307. <p>C’est important pour <a href="/david/2024/02/29/" title="Wikipédia">diverses</a> <a href="/david/2024/03/07/" title="Sondages">raisons</a>.</p>
  308. <a href="#hr-139" title="Lien vers cette section de la page"><hr id="hr-139" /></a>
  309. <blockquote lang="en">
  310. <p>I think it’s time to admit that my hobby is buying stuff for my new&nbsp;hobby.</p>
  311. <p><cite><em>Chris Hallbeck</em>, <a data-link-domain="mastodon.social" href="https://mastodon.social/@Chrishallbeck/112153815963114685">sur&nbsp;masto</a></cite></p>
  312. </blockquote>
  313. <p><em>Touché.</em></p>
  314. <nav>
  315. <p>
  316. <a href="/david/2024/apprentissage/"
  317. title="Liste de tous les articles 2024 associés à cette étiquette"
  318. rel="tag">#apprentissage</a>
  319. <a href="/david/2024/technique/"
  320. title="Liste de tous les articles 2024 associés à cette étiquette"
  321. rel="tag">#technique</a>
  322. <a href="/david/2024/web/"
  323. title="Liste de tous les articles 2024 associés à cette étiquette"
  324. rel="tag">#web</a>
  325. <a href="/david/2024/#tags" title="Liste de toutes les étiquettes 2024">tous ?</a>
  326. </p>
  327. </nav>
  328. <nav>
  329. <p>
  330. <a rel="prev"
  331. href="/david/2024/03/25/"
  332. title="Publication précédente : Inclusion">← Précédent</a> •
  333. <a href="/david/2024/" title="Liste des publications récentes">↑ En 2024</a>
  334. • <a rel="next"
  335. href="/david/2024/03/28/"
  336. title="Publication suivante : Collectif">Suivant →</a>
  337. </p>
  338. </nav>
  339. <form action="/david/recherche/" method="get">
  340. <fieldset>
  341. <legend>Recherche</legend>
  342. <label for="input-search">Termes de votre recherche :</label>
  343. <input id="input-search" type="search" name="s" aria-describedby="indexation-infos" required>
  344. <input type="submit" value="Chercher">
  345. <p id="indexation-infos">
  346. <small>
  347. Seuls les contenus de ces 8 dernières années sont indexés.
  348. </small>
  349. </p>
  350. </fieldset>
  351. </form>
  352. <aside>
  353. <theme-toggle></theme-toggle>
  354. </aside>
  355. </article>
  356. <hr>
  357. <footer>
  358. <p>
  359. <a href="/david/" title="Aller à l’accueil">Accueil</a>
  360. <a href="/david/log/" title="Accès au flux RSS">Suivre</a>
  361. <a href="http://larlet.com"
  362. title="Go to my English profile"
  363. data-instant>Pro</a>
  364. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">Email</a>
  365. <abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">Légal</abbr>
  366. </p>
  367. <template id="theme-selector">
  368. <form>
  369. <style type="text/css">
  370. fieldset div {
  371. text-align: center;
  372. }
  373. </style>
  374. <fieldset>
  375. <legend>Thème</legend>
  376. <div>
  377. <label>
  378. <input type="radio" value="auto" name="chosen-color-scheme" checked>
  379. Auto
  380. </label>
  381. <label>
  382. <input type="radio" value="dark" name="chosen-color-scheme">
  383. Foncé
  384. </label>
  385. <label>
  386. <input type="radio" value="light" name="chosen-color-scheme">
  387. Clair
  388. </label>
  389. </div>
  390. </fieldset>
  391. </form>
  392. </template>
  393. </footer>
  394. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  395. <script>
  396. class ThemeToggle extends HTMLElement {
  397. constructor() {
  398. super()
  399. const themeSelectorTemplate = document.querySelector('#theme-selector')
  400. const form = themeSelectorTemplate.content.firstElementChild
  401. this.attachShadow({ mode: 'open' })
  402. this.shadowRoot.appendChild(form.cloneNode(true))
  403. }
  404. connectedCallback() {
  405. const form = this.shadowRoot.querySelector('form')
  406. form.addEventListener('change', (e) => {
  407. const chosenColorScheme = e.target.value
  408. localStorage.setItem('theme', chosenColorScheme)
  409. toggleTheme(chosenColorScheme)
  410. })
  411. const selectedTheme = localStorage.getItem('theme')
  412. if (selectedTheme && selectedTheme !== 'undefined') {
  413. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  414. }
  415. }
  416. }
  417. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  418. window.addEventListener('load', () => {
  419. let colorsLayer = undefined
  420. let hasDarkRules = false
  421. for (const styleSheet of Array.from(document.styleSheets)) {
  422. let mediaRules = []
  423. for (const layerRule of styleSheet.cssRules) {
  424. if (!(layerRule instanceof CSSLayerBlockRule)) {
  425. continue
  426. }
  427. if (layerRule.name === 'colors') {
  428. colorsLayer = layerRule
  429. }
  430. for (const cssRule of layerRule.cssRules) {
  431. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  432. continue
  433. }
  434. // WARNING: Safari does not have/supports `conditionText`.
  435. if (cssRule.conditionText) {
  436. if (cssRule.conditionText !== prefersColorSchemeDark) {
  437. continue
  438. }
  439. } else {
  440. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  441. continue
  442. }
  443. }
  444. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  445. }
  446. }
  447. // WARNING: do not try to insert a Rule to a styleSheet you are
  448. // currently iterating on, otherwise the browser will be stuck
  449. // in a infinite loop…
  450. for (const mediaRule of mediaRules) {
  451. // Safari requires the `0` second parameter (even if default).
  452. colorsLayer.insertRule(mediaRule.cssText, 0)
  453. hasDarkRules = true
  454. }
  455. }
  456. if (hasDarkRules) {
  457. if ('customElements' in window && !customElements.get('theme-toggle')) {
  458. customElements.define('theme-toggle', ThemeToggle)
  459. }
  460. }
  461. })
  462. </script>
  463. <script src="/static/david/js/popper-2.11.8.min.js"></script>
  464. <script src="/static/david/js/tippy-bundle-6.3.7.umd.min.js"></script>
  465. <script>
  466. tippy('[data-tippy]', {
  467. content(reference) {
  468. reference.addEventListener('click', (e) => e.preventDefault())
  469. return `
  470. <h3 lang="fr">
  471. <img src="${reference.dataset.favicon}" loading="lazy">
  472. <a href="${reference.dataset.source}"
  473. >Article sur ${reference.dataset.domain}</a></h3>
  474. <p lang="${reference.hreflang}"><em>${reference.dataset.description}</em></p>
  475. <div class="tippy-links" lang="fr">
  476. <a href="${reference.href}">Archive au ${reference.dataset.date}</a>
  477. </div>
  478. `
  479. },
  480. allowHTML: true,
  481. interactive: true,
  482. delay: [150, 700],
  483. hideOnClick: false
  484. })
  485. </script>
  486. <script type="module">
  487. import { annotate } from '/static/david/js/rough-notation-0.5.1.esm.min.js'
  488. const markObserver = new IntersectionObserver((entries, observer) => {
  489. const computedStyle = getComputedStyle(document.documentElement)
  490. const markBackground = computedStyle.getPropertyValue('--mark-background')
  491. for (const entry of entries) {
  492. if (entry.intersectionRatio === 0) continue
  493. const markElement = entry.target
  494. markElement.style.backgroundColor = 'inherit'
  495. const annotation = annotate(
  496. markElement, {
  497. type: 'highlight',
  498. multiline: true,
  499. color: markBackground,
  500. // animate: !window.matchMedia('(prefers-reduced-motion: reduce)').matches
  501. animate: false
  502. }
  503. )
  504. annotation.show()
  505. observer.unobserve(markElement)
  506. }
  507. }, {threshold: 1.0})
  508. for (const markElement of document.querySelectorAll('mark')) {
  509. markObserver.observe(markElement)
  510. }
  511. </script>
  512. </body>
  513. </html>