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

1 jaar geleden
1 jaar geleden
1 jaar geleden
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  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>A highly opinionated guide to learning about ActivityPub (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://tinysubversions.com/notes/reading-activitypub/">
  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 highly opinionated guide to learning about ActivityPub</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://tinysubversions.com/notes/reading-activitypub/" title="Lien vers le contenu original">Source originale</a>
  70. </p>
  71. </nav>
  72. <hr>
  73. <p><em>by Darius Kazemi, Jun 1, 2019</em></p>
  74. <p>This document is for programmers who take one look at <a href="https://activitypub.rocks">activitypub.rocks</a>, click on through to the documentation, and can't make heads or tails of it.</p>
  75. <p>In other words this document is for me, one year ago.</p>
  76. <p><strong>IMPORTANT NOTE: This document does not explain ActivityPub. It explains <em>how to learn about ActivityPub</em>.</strong></p>
  77. <p>If you want THE ULTIMATE TL;DR, just <a href="#the-ultimate-tl-dr">skip to that section</a>.</p>
  78. <h2 id="introduction">Introduction</h2>
  79. <p>The one big misunderstanding that a programmer can have when attempting to learn about ActivityPub is to assume that all the information you need is in the ActivityPub spec! Really, to learn about ActivityPub in such a way that it will be useful in a concrete sense, you need to be familiar with three specs:</p>
  80. <p>Those two extra documents are in fact mentioned in the ActivityPub spec itself, so I'm not claiming that they are somehow hidden. But I'm a lazy, impatient reader. When I'm given a spec like ActivityPub that is ~10,000 words of spec language... well. I'm going to try and take all the shortcuts I can.</p>
  81. <p>I thought I was the only programmer with this problem, but as I spoke to more and more people at conferences, I realized there are many others who, for example, look at ActivityPub and yet have no idea there exists an Activity Vocabulary spec.</p>
  82. <h3 id="start-with-activity-vocabulary">Start with Activity Vocabulary</h3>
  83. <p>I am a concrete thinker. I don't like reading about a system for passing messages between computers if I can't have some sort of understanding of <em>what exactly</em> is being passed around.</p>
  84. <p>Unfortunately, the ActivityPub spec is mostly all about the "publication" part and not the "what data are we sending around" part.</p>
  85. <p>For this reason, <strong>I recommend that you start your journey into ActivityPub by first reading the <a href="https://www.w3.org/TR/activitystreams-vocabulary/">Activity Vocabulary spec</a></strong>.</p>
  86. <h2 id="reading-activity-vocabulary">Reading Activity Vocabulary</h2>
  87. <p>This spec is in five non-appendix sections:</p>
  88. <ol>
  89. <li>Introduction</li>
  90. <li>Core Types</li>
  91. <li>Extended Types</li>
  92. <li>Properties</li>
  93. <li>Implementation Notes</li>
  94. </ol>
  95. <h3 id="introduction">Introduction</h3>
  96. <p>The intro is brief, and is your usual spec boilerplate about the definitions of MUST and SHOULD.</p>
  97. <h3 id="core-types">Core Types</h3>
  98. <p>"Core Types" is where you might think you should start. It's the core, right? Well, you're wrong. The first thing you're hit with is a definition of <strong>Object</strong>:</p>
  99. <blockquote>
  100. <p>Describes an object of any kind. The Object type serves as the base type for most of the other kinds of objects defined in the Activity Vocabulary, including other Core types such as Activity, IntransitiveActivity, Collection and OrderedCollection. </p>
  101. </blockquote>
  102. <p>The example given is:</p>
  103. <pre><code>{
  104. "@context": "https://www.w3.org/ns/activitystreams",
  105. "type": "Object",
  106. "id": "http://www.test.example/object/1",
  107. "name": "A Simple, non-specific object"
  108. }
  109. </code></pre>
  110. <p>Wow. That's real useful. Now I know how to define a non-specific thing with no properties.</p>
  111. <p>(Aside: this "begin from first principles" thing is a problem with almost all spec writing. It is largely <em>how specs are written</em> and it fucking blows. The authors of these documents are just following tradition and I do not wish to paint these specs as particularly bad.)</p>
  112. <p>Just skip the Core Types for now and move onto section 3, Extended Types.</p>
  113. <h3 id="extended-types">Extended Types</h3>
  114. <p>Ah, Extended Types. Or as I like to call them: the actually-useful types. Something like 50% of the stuff you actually care about is in this section.</p>
  115. <p>We are given three major kinds of "types". (Um, by the way, this isn't quite a "type" as you may know it. It's really more like a JSON data format.)</p>
  116. <ul>
  117. <li>Activity Types</li>
  118. <li>Actor Types</li>
  119. <li>Object Types</li>
  120. </ul>
  121. <h4 id="activity-types">Activity Types</h4>
  122. <p>We are once again immediately hit over the head with this doozy of a sentence:</p>
  123. <blockquote>
  124. <p>All Activity Types inherit the properties of the base <strong>Activity</strong> type.</p>
  125. </blockquote>
  126. <p>While this does make sense in a highly specific way that is unique to specification writing, please just skip this sentence and all sentences like it (there is at least one other saying the same thing about Object Types).</p>
  127. <p>Best to avoid the near-tautological definitions required for writing things like parsers and instead skip to the concrete stuff. Scroll down and you'll see that Activity Types include things like:</p>
  128. <p>Hey!! This is good! This is like... stuff that users do on social networks. Now we're getting somewhere.</p>
  129. <p>Browse over all the activities. Look at the "Notes" section and glance at the example JSON object. There are some interesting activity types in there! For example, there's <strong>Question</strong>, which is the activity type that Mastodon uses in its implementation of polls. And there's <strong>Travel</strong>, which is meant to indicate movement from geographic (or even conceptual) point A to point B! You might make extensive use of this if your social network supported a Facebook style "I've checked in to New York City right now" post. There's even a <strong>View</strong> activity, which can be used for read receipts.</p>
  130. <p>Anyway, read 'em.</p>
  131. <h4 id="actor-types">Actor Types</h4>
  132. <p>An <strong>Actor</strong> is what you might think of as a user agent. It can be a person, a bot, a company, a group of people, and more. But the important thing is: an <strong>Actor</strong> is the <em>thing that is doing</em> an <strong>Activity</strong>.</p>
  133. <p>So a normal post to Mastodon is a <strong>Person</strong> actor doing a <strong>Create</strong> activity (what is being created is defined in the next section, Object types). Or if a company sent out discount codes to its followers, that would be an <strong>Organization</strong> doing an <strong>Offer</strong> activity.</p>
  134. <p>There aren't many <strong>Actor</strong> types listed so just read them all.</p>
  135. <h4 id="object-types">Object Types</h4>
  136. <p>Yeah. I know. "Object" is probably the most overloaded English word in programming and we're already on our second thing called an object in this one spec. I'm sorry. Deep breaths, we'll get through this.</p>
  137. <p>Anyhow. These are the meat and potatoes of what actually gets sent around these networks. Object types include:</p>
  138. <p>...and several more. So when you make a regular text post to Mastodon, that is a case where a <strong>Person</strong> will <strong>Create</strong> a <strong>Note</strong>.</p>
  139. <h4 id="putting-it-all-together">Putting it all together</h4>
  140. <p>Here's some code from <a href="https://github.com/dariusk/express-activitypub/">a small ActivityPub reference implementation I wrote</a></p>
  141. <pre><code> let noteMessage = {
  142. 'id': `https://${domain}/m/${guidNote}`,
  143. 'type': 'Note',
  144. 'published': d.toISOString(),
  145. 'attributedTo': `https://${domain}/u/${name}`,
  146. 'content': text,
  147. 'to': ['https://www.w3.org/ns/activitystreams#Public'],
  148. };
  149. let createMessage = {
  150. '@context': 'https://www.w3.org/ns/activitystreams',
  151. 'id': `https://${domain}/m/${guidCreate}`,
  152. 'type': 'Create',
  153. 'actor': `https://${domain}/u/${name}`,
  154. 'to': ['https://www.w3.org/ns/activitystreams#Public'],
  155. 'cc': [follower],
  156. 'object': noteMessage
  157. };
  158. </code></pre>
  159. <p>I construct the <strong>Note</strong> object and the <strong>Create</strong> activity. The <strong>Note</strong> object gets embedded inside the <strong>Create</strong> activity, which indicates that we are <strong>Creat</strong>-ing a <strong>Note</strong>. What about the <strong>Person</strong>? That's implied in the 'actor' field of the <strong>Create</strong>, and that URL is the equivalent of passing the <strong>Person</strong> by reference. Any program parsing this out could query that URL and get the JSON of the <strong>Person</strong> from there.</p>
  160. <h3 id="properties">Properties</h3>
  161. <p>If you're wondering what those "id", "cc", and so on fields mean, you're in luck. Those are described in this section, which contains the <em>other</em> 50% of the stuff you care about.</p>
  162. <p>Read this section, if only to familiarize yourself with what's here. That way you'll know to come back here when you see something like "prev" in a JSON object and wonder how that's supposed to be constructed.</p>
  163. <h3 id="implementation-notes">Implementation Notes</h3>
  164. <p>This is where the concrete examples of things like "how to represent a friend request" live. Definitely worth reading this section as well, though on a first run through you really only need to read:</p>
  165. <ul>
  166. <li>5.1: Audience targeting (not about advertising, this is about what the "to" and "cc" fields mean. It's the difference between a direct message and a public message, for example.)</li>
  167. <li>5.2.1: Modeling a "friend request"</li>
  168. <li>5.5: How <strong>Undo</strong> does and does not work</li>
  169. <li>5.8: A nice conceptual grouping of what circumstances you might use different Activities in</li>
  170. </ul>
  171. <h2 id="reading-activitypub">Reading ActivityPub</h2>
  172. <p>Next, read <a href="https://www.w3.org/TR/activitypub/">the ActivityPub spec</a>. It's in seven major sections:</p>
  173. <ol>
  174. <li>Overview</li>
  175. <li>Conformance</li>
  176. <li>Objects</li>
  177. <li>Actors</li>
  178. <li>Collections</li>
  179. <li>Client to Server Interactions</li>
  180. <li>Server to Server Interactions</li>
  181. </ol>
  182. <p>But first, a word about JSON-LD.</p>
  183. <h3 id="a-note-on-json-ld">A note on JSON-LD</h3>
  184. <p>Starting with the ActivityPub spec, you're going to see <a href="https://json-ld.org/">JSON-LD</a> mentioned a bunch. You don't have to care about this at all. For our purposes it's JSON with some specific property names in it, and where you follow links to complete the content of an object. Is this technically 100% correct? No. But if you were the kind of person who cared about the difference between JSON-LD and JSON, you'd probably be writing your own spec right now instead of reading this article.</p>
  185. <h4 id="following-links-in-json">Following links in JSON</h4>
  186. <p>One important thing to note about the kind of JSON stuff you'll be seeing is that you'll often see a URI embedded in a JSON object. Take the following example of an object with no URI anywhere in it:</p>
  187. <pre><code>{
  188. "id": 1234,
  189. "object": {
  190. "type": "Note",
  191. "content": "Hello world."
  192. }
  193. }
  194. </code></pre>
  195. <p>Other times you'll see something like:</p>
  196. <pre><code>{
  197. "id": 1234,
  198. "object": "https://example.com/5678"
  199. }
  200. </code></pre>
  201. <p>But then if you do a GET request with the appropriate headers to "<a href="https://example.com/5678">https://example.com/5678</a>", you'll get</p>
  202. <pre><code>{
  203. "type": "Note",
  204. "content": "Hello world."
  205. }
  206. </code></pre>
  207. <p>These two JSON things comprise the exact same information as that first JSON thing. It's just split across two HTTP requests. When you encounter a URI in one of these JSON objects, the idea is to follow the URI in order to resolve it into plain JSON and consider that the sub-object.</p>
  208. <h3 id="overview">Overview</h3>
  209. <p>Read this whole thing. You probably tried to read this once, before reading the Vocabulary spec, and if you're anything like me you were extremely confused. I promise it will be less confusing this time.</p>
  210. <p>Basically it explains that you're taking these JSON things described in the Vocabulary spec and POSTing them to specific API endpoints, which process them.</p>
  211. <h3 id="conformance">Conformance</h3>
  212. <p>A short section that you might as well read. This is where it's made clear that this document actually describes <em>two separate protocols</em>. There is a Client to Server (C2S) and Server to Server (S2S).</p>
  213. <p>In order to make your service "federated" via ActivityPub, <em>the only protocol you really care about is the server to server protocol</em>. S2S is how a Mastodon server can talk to a Pleroma server can talk to a Pixelfed server etc etc. Federation as we know it.</p>
  214. <p>The C2S protocol is about <em>user choice</em>. It's a good thing, but it has nothing to do with federation. It's a standard way for user client software, like a phone app, to talk to a server. If we lived in a world where Mastodon and Pleroma and Pixelfed all used the C2S protocol, then in theory I could easily write a phone app that connects to all three of those services, and users could mix and match clients with servers to their heart's delight. A new service could pop up that I didn't even know about, but if it used C2S my phone app would be minimally compatible with it. (We do not live in this world, most federated services do not implement C2S, but this is the vision, to my understanding.)</p>
  215. <h3 id="objects">Objects</h3>
  216. <p>This will be a much easier read now that you've read over the Vocabulary document.</p>
  217. <p>Specifically, sections 3, 3.1, and 3.2 are really important and discuss why, for example, if you send a message to a remote server, it should have some way of querying your server to confirm the message. I originally thought that I could get away with just sending messages into the void and not storing them in a local database, but I was wrong, because remote servers need to confirm that it's not just some random person claiming to send content from my own origin.</p>
  218. <h3 id="actors">Actors</h3>
  219. <p>So an ActivityPub actor is an Activity Vocabulary actor, with the mandatory addition of an inbox and and outbox, which are typically URIs that you either GET or POST to depending on what you want to do with them. For example, you can GET from a user's outbox URI and receive a JSON list of posts. Try it on my outbox from Friend Camp right now in your web browser by <a href="https://friend.camp/users/darius/outbox">browsing to this URL</a>. You should see something like this:</p>
  220. <pre><code>{
  221. "@context":"https://www.w3.org/ns/activitystreams",
  222. "id":"https://friend.camp/users/darius/outbox",
  223. "type":"OrderedCollection",
  224. "totalItems":5688,
  225. "first":"https://friend.camp/users/darius/outbox?page=true",
  226. "last":"https://friend.camp/users/darius/outbox?min_id=0&amp;page=true"
  227. }
  228. </code></pre>
  229. <p>If you click through to <a href="https://friend.camp/users/darius/outbox?min_id=0&amp;page=true">the "last" URI</a>, you'll get 20 of my oldest posts from Friend Camp, including <a href="https://friend.camp/@darius/100538943364185987">this charmer</a>.</p>
  230. <p>Anyway, read this whole section too. It's short.</p>
  231. <h3 id="collections">Collections</h3>
  232. <p>Skip this for now. You'll absolutely want to come back to this later but you can probably figure out what collections are in context of their usage (hint: that outbox JSON is a type of collection; it contains paging data for iterating through collections of stuff).</p>
  233. <p>But seriously just skip this for now, you don't need to muddy your brain with it.</p>
  234. <h3 id="client-to-server-interactions">Client to Server Interactions</h3>
  235. <p>Look. If you want to support a universal client ecosystem, go ahead and read this section. But if you just want to write a federated service, skip it and come back to it later.</p>
  236. <h3 id="server-to-server-interactions">Server to Server Interactions</h3>
  237. <p>Read this whole thing. This one section, combined with the Activity Vocabulary spec, forms the absolute basics of getting a federated service up and running. The Vocabulary spec tells you <em>what</em> to send, and this section tells you <em>how</em> to send it.</p>
  238. <p>Among other things, it discusses the difference between an <code>inbox</code> and a <code>sharedInbox</code>. It talks about how to <strong>Follow</strong> an <strong>Actor</strong>, and how someone might <strong>Accept</strong> or <strong>Reject</strong> a follow request. It talks about <strong>Announce</strong>, which in addition to literally being for announcements, is also your standard "sharing" activity, like an RT on Twitter or a boost on Mastodon.</p>
  239. <h2 id="reading-activitystreams-2-0">Reading ActivityStreams 2.0</h2>
  240. <p>Eh. Don't read it now. Skim the table of contents. There will be bits of ActivityPub that don't make sense without the ActivityStreams 2.0 spec, but those will be more advanced questions you'll have later.</p>
  241. <h2 id="the-ultimate-tl-dr">THE ULTIMATE TL;DR</h2>
  242. <ul>
  243. <li>Read the <a href="https://www.w3.org/TR/activitystreams-vocabulary/">Activity Vocabulary spec</a>, sections 3, 4, 5.1, 5.2.1, 5.5, and 5.8</li>
  244. <li>Read the <a href="https://www.w3.org/TR/activitypub/">ActivityPub spec</a>, sections 1, 2, 3, 4, and 7</li>
  245. <li>Familiarize yourself with the table of contents for the <a href="https://www.w3.org/TR/activitystreams-core/">ActivityStreams 2.0 spec</a>.</li>
  246. </ul>
  247. <h2 id="go-forth-and-make-stuff">Go forth and make stuff</h2>
  248. <p>Assuming you&#39;ve done all this reading, the knowledge you have right now should hopefully be enough to start hacking on sending simple messages via ActivityPub.</p>
  249. <p>I recommend reading these two articles by Eugen Rochko, the creator of Mastodon:</p>
  250. <ul>
  251. <li><a href="https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/">How to implement a basic ActivityPub server</a></li>
  252. <li><a href="https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/">How to make friends and verify requests</a></li>
  253. </ul>
  254. <p>If you&#39;re like me and you enjoy poking around at a simple implementation, I have written a bare bones JavaScript ActivityPub server using the popular Express application framework:</p>
  255. <ul>
  256. <li><a href="https://github.com/dariusk/express-activitypub">Express ActivityPub Server</a></li>
  257. </ul>
  258. </article>
  259. <hr>
  260. <footer>
  261. <p>
  262. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  263. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  264. </svg> Accueil</a> •
  265. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  266. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  267. </svg> Suivre</a> •
  268. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  269. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  270. </svg> Pro</a> •
  271. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  272. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  273. </svg> Email</a> •
  274. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  275. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  276. </svg> Légal</abbr>
  277. </p>
  278. <template id="theme-selector">
  279. <form>
  280. <fieldset>
  281. <legend><svg class="icon icon-brightness-contrast">
  282. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  283. </svg> Thème</legend>
  284. <label>
  285. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  286. </label>
  287. <label>
  288. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  289. </label>
  290. <label>
  291. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  292. </label>
  293. </fieldset>
  294. </form>
  295. </template>
  296. </footer>
  297. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  298. <script>
  299. function loadThemeForm(templateName) {
  300. const themeSelectorTemplate = document.querySelector(templateName)
  301. const form = themeSelectorTemplate.content.firstElementChild
  302. themeSelectorTemplate.replaceWith(form)
  303. form.addEventListener('change', (e) => {
  304. const chosenColorScheme = e.target.value
  305. localStorage.setItem('theme', chosenColorScheme)
  306. toggleTheme(chosenColorScheme)
  307. })
  308. const selectedTheme = localStorage.getItem('theme')
  309. if (selectedTheme && selectedTheme !== 'undefined') {
  310. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  311. }
  312. }
  313. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  314. window.addEventListener('load', () => {
  315. let hasDarkRules = false
  316. for (const styleSheet of Array.from(document.styleSheets)) {
  317. let mediaRules = []
  318. for (const cssRule of styleSheet.cssRules) {
  319. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  320. continue
  321. }
  322. // WARNING: Safari does not have/supports `conditionText`.
  323. if (cssRule.conditionText) {
  324. if (cssRule.conditionText !== prefersColorSchemeDark) {
  325. continue
  326. }
  327. } else {
  328. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  329. continue
  330. }
  331. }
  332. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  333. }
  334. // WARNING: do not try to insert a Rule to a styleSheet you are
  335. // currently iterating on, otherwise the browser will be stuck
  336. // in a infinite loop…
  337. for (const mediaRule of mediaRules) {
  338. styleSheet.insertRule(mediaRule.cssText)
  339. hasDarkRules = true
  340. }
  341. }
  342. if (hasDarkRules) {
  343. loadThemeForm('#theme-selector')
  344. }
  345. })
  346. </script>
  347. </body>
  348. </html>