Update website theme etc

This commit is contained in:
Alexis Métaireau 2024-03-15 02:15:11 +01:00
parent b69f43febb
commit ae28650abd
34 changed files with 949 additions and 129 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cSpell.language": "en,fr"
}

View file

@ -0,0 +1,22 @@
# Conflits
Ce matin, jécrivais:
> Je ne m'étais pas rendu compte que je passais mon temps entre la gestion de conflits humains, et la gestion des conflits dans la sphère technique, ça me donne envie de creuser le parallèle.
> [Notes hebdo](notes hebdo #17)
Le fait que les CRDTs (qu'on pourrait traduire par « des types de données qui convergent sans générer de conflits ») proposent une convergence/résolution en évitant les conflits me fait à la fois rêver et m'effraie quand je projette ça sur les conflits humains.
Je me demande à quoi ça ressemblerait si on essayait de projeter les outils dun monde sur lautre ?
## Quest-ce quun conflit ?
A ce stade de la discussion, il semble utile de définir ce que jentends par conflit. Peut-être que le conflit pourrait être une divergence dopinion difficile à réconcilier ?
Je loppose dun côté à la divergence dopinions, qui elle peut être conciliable facilement — justement — et de lautre côté à lagression, qui est un moyen de cliver le débat, de chercher à lemporter sur lautre.
## Les conflits inutiles
Un de mes angles morts de ces dernières années était de ne pas
Mais il y a une autre manière de voir les choses: on peut éviter les conflits inutiles, si on utilise une approche similaire à celle du CRDT: avoir des règles en place, qui sappliquent. Lidée étant plus davoir un moyen de décider lors des cas qui ne générent pas du conflit.

View file

@ -0,0 +1,216 @@
---
title: Oser la confiance
author: Bertrand Martin, Vincent Lenhardt, Bruno Jarrosson
status: draft
---
On m'a conseillé ce livre, voici donc quelques notes. Je ne partais pas gagnant
parce que j'avais un peu peur de l'approche très « managériale », mais (bien
qu'elle soit présente) ce n'était pas bloquant pour moi. J'ai eu quelques
belles surprises au passage.
Le tout est quand même emprunt d'une vision capitaliste du monde, dans laquelle
la croissance des entreprises est indispensable. Ça mis de côté, plein de bons bouts.
> Ainsi, peu à peu, sont sorties les conditions de la libération de l'énergie:
>
> - La franchise sur la réalité de la situation.
> - L'appel à tous, qui reconnaît la valeur de chacun et solidarise l'entreprise.
> - Pas d'exclu.
> - L'écoute préalable à toute déclaration pour ouvrir le champ sans limites.
> - La liberté jusqu'au bout, sans contrôle, qui responsabilise et permet l'appropriation.
> - La confiance jusqu'au bout qui appelle la confiance.
>
> page 33.
Une liste intéressante à plein d'égards. Il est rappelé à plusieurs endroits le
rôle de la « confiance jusqu'au bout », qu'on peut opposer à la méfiance, je crois.
---
> Cette confiance, qui naît et se nourrit de réciprocité n'est-elle pas une
folie ? C'est pourtant bien dans ce regard que l'autre peut se construire. Et la
méfiance, si minime soit-elle, est destructrice.
>
> Permettre l'appropriation qui engendre la motivation, c'est accepter de
se dépouiller. **Est-il acceptable, est-il même normal pour celui qui a la
responsabilité du pouvoir d'abandonner ainsi son pouvoir, d'en abandonner
l'exercice, même en partie ? Le pouvoir est si agréable.**
>
> Respecter la parole et la pensée de l'autre, lui laisser cet espace de liberté
qui lui permettra d'exercer ses talents et sa responsabilité, de développer son
jugement, est bien difficile lorsque l'on croit détenir la vérité. Il est bien
réconfortant de croire que l'on sait. Un chef d'entreprise me disait : « Le plus
difficile pour moi, c'est de me taire lorsque je connais la solution… »
>
> Donner l'autonomie, la liberté qui va permettre la croissance de la personne,
n'est-ce pas prendre le risque de l'erreur, de l'accident, de l'incohérence ?
Casser l'ordre qui fige, qui empêche d'avancer, laisser s'établir et durer un
désordre chaotique d'où pourra jaillir la vie, est-ce bien acceptable ?
>
> **Ces questions [...] sont des interrogations jamais closes comme des appels, des
fenêtres toujours ouvertes vers un avenir meilleur. Elles dépendent [...] de nos
convictions, mais aussi de nos limites et de nos peurs.
>
> Tenter d'y répondre, c'est accepter de se remettre en question et avancer,
c'est un cheminement individuel et collectif où chaque pas assuré, chaque étape
atteinte, est un nouveau point de départ.
>
> — page 65.
---
> Il s'agit [...] de retrouver ce que l'on appelle la combinaison
de la Protection, de la Permission, et de la Puissance [en analyse
transactionnelle].
>
> Par Protection, on entend toutes les interventions qui
définissent les règles du jeu, les valeurs qui vont être mises en œuvre
(confiance, transparence, language de vérité, respect des personnes, désir
d'encouragement, considération de l'autre, écoute…). **Cette Protection est aussi
la garantie qu'en cas de difficulté, de fragilité ou d'erreur, la personne
trouvera un appui**, conformément à une règle du jeu clairement définie [...] de façon a
protéger l'équipe, tant des dangers venant de l'extérieur que de ses propres
fragilités internes.
>
> De même que l'on ne part pas en montagne sans cordes et matériel pour éviter
les accidents, la prise de risque nécessaire au changement doit être « assurée »,
« protégée ».
Je ne connais pas bien l'analyse transactionnelle, mais je m'en méfie un peu,
surement plus par ignorance que par connaissance.
Je trouve cependant que les différents « P » (Protection, Permission, Puissance) qui sont
proposés sont intéressants quand il s'agit de parler de confiance, de légitimité
et de collaboration. Je suis content de rencontrer ces termes.
> [...] La Permission est [...] le pouvoir de dire oui, ce qui suppose de la
part des protagonistes [...] une attitude de « parent nourricier » qui soutient,
encourage, met de la chaleur, crée de la confiance, met en œuvre chez les
personnes l'effet « Pygmalion ».
Il évoque la métaphore de « la belle au bois dormant » (je paraphrase)
1. Le poison commence à se diriger vers le cœur
2. Une bonne fée l'endort pour la protégér
3. Un prince charmant veut la réveiller, pensant la sauver, mais…
4. Quand elle se réveille, le poison continue son chemin et la tue.
> Combien de formateurs [...] se prévalent de ce beau rôle [...] et amènent les
individus ou les organisations à vivre des exp^ériences apparemment souhaitables
ou désirées dont l'issue est plus ou moins fatale. On invite les personnes à
s'exprimer et on les sanctionne ensuite.
> Non ! Attention: pas de Permission sans Protection préalable.
>
> page 70-71.
---
> Le circuit de la confiance
>
> - Protection et permission visant à la confiance
> - Régulation (partage des représentations)
> - Subtilité, reconnaissance de l'autre comme sujet
> - Intimité, partage des confidences
> - Solidarité
> - Créativité, résolution de problèmes
> - Opérationnalité débloquée et performante
> - Résultats et feedback
> - Satisfaction, motivation
> - retour à la première étape
>
> page 78
---
## Faire équipe
<div class="align-center">
<img src="/images/confiance/equipe-performante.jpg" />
</div>
> Pour qu'une équipe soit performante, elle doit successivement **franchir ces trois étapes**:
>
> 1. La collection d'individus où **les gens sont centrés** sur une technique, une
> fonction relativement individualisée, **sur une performance personnelle** et une
> dynamique souvent encombrée par la logique des territoires.
> 2. Le groupe solidaire intégrant des logiques différentes et passant d'une
> monologique à une dialogique [au sens d'Edgar Morin], optimisant des décisions
> dans des processus transverses. **Il se crée donc une solidarité, une écoute
> réciproque et le désir de dépasser des points de vue souvent opposés ou
> contradictoires, de sortir de la logique des territoires**.
> 3. Dans l'équipe performante, troisième étape, **les personnes ont suffisamment
> conscience de leur identité et de leur complémentarité pour pouvoir la dépasser
> et se centrer sur le sens et la vision commune**. Chacun se sent porteur du tout et
> vit une approche de type holomorphique; chaque fonction est porteuse du tout et
> chacun se sent responsable de la pérénité [du groupe].
J'aime bien cette vision de l'évolution d'une équipe, et j'ai l'impression de
pouvoir la voir parfoisà l'œuvre. Content de passer les étapes.
## Le déficit d'identité
> Si la personne n'a pas trouvé son identité et la sécurité ontologique de
celui qui sait qui il est, il va se produire une interaction parasitaire, où
l'identité, insuffisante, conduit la personne à s'investir désespérément dans un
niveau de pouvoir qui se nourrit d'au moins sept niveaux de réalité [conscient,
inconscient, opérationnel, fonction, entreprise, environnement, metasens]
> Prenons l'exemple d'une réunion, qui a un ordre du jour indiqué dans le niveau
opérationnel: au lieu de se concentrer sur la résolution d'un problème ou la
prise de décision opérationnelle, l'enjeu et l'investissement de la personne
déficiente en identité (ou en mutation déstabilisante pour elle-même), va se
récupérer au niveau du pouvoir ; elle va chercher à asseoir son pouvoir et le
nourrir de tout ce qui peut se passer au niveau relationnel, ou dans des jeux
plus ou moins inconscients, ou bien dans une guerre de territoires où elle
va surinvestir la définition de sa fonction ; ou bien elle va se positionner
dans l'entreprise et tirer les objectifs de l'entreprise au profit de ses
oibjectifs personnels, ou bien utiliser tout ce qui vient de son environnement
pour renforcer son pouvoir, ou enfin justifier son attitude de pouvoir par
des élements venants du métasens (politique, culture, éthique, spirituel,
etc).
>
> **Il sera donc nécessaire pour la personne qui gère le changement de
prendre en compte ce déficit identitaire**, ce surinvestissement du niveau de
pouvoir et de faire le lien entre les neufs niveaux de sens [environnemental,
organisationnel, managérial, professionnel, vie privée, psychologique,
existentiel, spirituel, confessionnel] pour dégager la problématique
opérationnelle de tout ce qui la parasite. **Ainsi, cette personne agira pour
traiter le problème là où se trouve le blocage du sens et seulement après,
reviendra à l'ordre du jour opérationnel**.
Je suis content de voir l'aspect pouvoir mis en regard avec l'aspect de déficit
d'identité, comme quoi on pourrait chercher à accroitre son pouvoir lorsqu'on ne
se sens pas bien dans son identité (au sein du travail).
Je me demande encore comment on peut faire en sorte de déjouer ces dynamiques de
manière efficace.
---
> [...] Les responsables se doivent d'être vigilants en face de toutes
les récupérations ou les manipulations dont ils peuvent être les objets ou les
sujets et veiller, par un questionnement éthique [...]
à ce que leur situation de pouvoir ne devienne pas le lieu où ils perdent un peu
ou beaucoup de leur âme. [...] **La question permanente reste toujours posée: quelle est
la vériftable finalité? Le bien commun, le service, ou l'égoïsme individuel** ou
celui d'une groupe mafieux ?
---
> Mettre l'essentiel au cœur de l'immportant ne consiste pas à faire de
l'entreprise une idole, une finalité en soi. Cela consiste au contraire à voir
que l'entreprise est un lieu de vie qui a aussi sa naissance et sa mort, qui
doit vivre ses cycles. Comme une école qui peut être un lieu de développement
transitoire des personnes, **l'entreprise ne trouvera son équilibre que dans la
mesure où son positionnement sera effectivement celui d'un lieu de croissance et
d'un lieu de vie**.
Bon, j'imagine que le côté « croissance » est questionnable, mais je pense qu'on
peut voir les choses sous l'angle positif de la croissance humaine, et non pas
uniquement économique.
> [...] Je pense effectivement que **les meilleurs seront ceux qui chercheront
dans l'entreprise non pas la finalité de leur existence, mais le lieu où ils
pourront développer au mieux leur propre potentiel**.

View file

@ -0,0 +1,343 @@
---
title: A comparison of different JavaScript CRDTs
status: draft
tags: crdts, umap, sync
display_toc: true
---
Collaboration is one of the most requested features on uMap since a long time. We've added a way to merge same-layers edit, but ideally we would love to make things easier to understand and more fluid for the users.
For this reason, I got more into CRDTs, with the goal of understanding how they work, what are the different libraries out there, and which one would be a good fit for us, if any.
So far, [the way I've though about collaboration features on uMap](https://blog.notmyidea.org/adding-collaboration-on-umap-third-update.html) is by:
- a) catching when changes are done on the interface ;
- b) sending messages to the other party and ;
- c) applying the changes on the receiving client.
This works well in general, but it doesn't take care of conflicts handling, especially when a disconnect can happen.
## Our requirements
We're looking for something that:
- **Stores and retrieves arbitrary key/value pairs**, like the name of the map, the default color of the markers, the type of layers, etc.
- **Propagates the changes** to the other parties, so they can see the edits in real-time ;
- **Handles disconnections**, so it's possible to work offline, or with an intermittent/bad connection.
In terms of API, we want something reactive and simple. I'm thinking about:
```js
let store = new Store({websocket:"wss://server"})
store.on((updates) => {
// do something with the updates
})
store.set('key', 'value')
```
## What are CRDTs anyway?
Conflict-free Resolution Data Types (CRDTs) are as a specific datatype able to merge its state with other states without generating conflicts. They handle consistency in distributed systems, making them particularly well-suited for collaborative real-time applications.
CRDTs ensure that multiple participants can make changes without strict coordination, and all replicas converge to the same state upon synchronization, without conflicts.
"Append-only sets" are probably one of the most common type of CRDT: if multiple parties add the same element, it will be present only once. It's our old friend `Set`.
### How can CRDT help in our case?
For uMap, CRDTs offer a solution to several challenges:
1. **Simultaneous Editing**: When multiple users interact with the same map, their changes must not only be reflected in real-time but also merged seamlessly without overwriting each other's contributions.
2. **Network Latency and Partition**: uMap operates over networks that can experience delays or temporary outages. CRDTs can handle these conditions gracefully, enabling offline editing and eventual consistency.
3. **Simplified Conflict Resolution**: Traditional methods often require complex algorithms to resolve conflicts, while CRDTs inherently minimize the occurrence of conflicts altogether.
4. **Decentralization**: While uMap currently relies on a central server, adopting CRDTs could pave the way for a more decentralized architecture, increasing resilience and scalability.
### How do CRDTs differ from traditional data synchronization methods?
Traditional data synchronization methods typically rely on a central source of truth, such as a server, to manage and resolve conflicts. When changes are made by different users, these traditional systems require a round-trip to the server for conflict resolution and thus can be slow or inadequate for real-time collaboration.
In contrast, CRDTs leverage mathematical properties (the fact that the datatypes can converge) to ensure that every replica independently reaches the same state, without the need for a central authority, thus minimizing the amount of coordination and communication needed between nodes.
This ability to maintain consistency sets CRDTs apart from conventional synchronization approaches and makes them particularly valuable for the development of collaborative tools like uMap, where real-time updates and reliability are important.
### Last Write Wins Registers
For managing key/value data, I'm leaning onto Last-Write-Wins (LWW) registers within CRDTs. With LWW, the main concern is establishing the sequence of updates. In a single-client scenario or with a central time reference, sequencing is straightforward. However, in a distributed environment, time discrepancies across peers can complicate things, as clocks may drift and lose synchronization.
To address this, CRDTs use vector clocks — a specialized data structure that helps to solve the relative timing of events across distributed systems and pinpoint any inconsistencies.
> A vector clock is a data structure used for determining the partial ordering of events in a distributed system and detecting causality violations.
>
> [Wikipedia](https://en.wikipedia.org/wiki/Vector_clock)
At first, I found CRDTs somewhat confusing, owing to their role in addressing complex challenges. CRDTs come in various forms, with much of their intricacy tied to resolving content conflicts within textual data or elaborate hierarchical structures. Fortunately for us, our use case is comparatively straightforward.
![CRDTs converging to the same state](/images/umap/crdt-converge.png)
Note that we could also use a library such as [rxdb](https://github.com/pubkey/rxdb) — to handle the syncing, offline, etc — because we have a master: we use the server, and we can use it to handle the merge conflicts. But by doing so, we also give more responsibility to the server, whereas when using CRDTs it's possible to do the merge only on the clients.
---
## The libraries
So, with this in mind, I've tried to have a look at the different libraries that are out there, and assess how they would work for us.
I'll be comparing these CRDTs, in the context of a JS mapping application, built with Leaflet :
- [Y.js](https://yjs.dev/)
- [Automerge](https://automerge.org/)
- [JSON Joy](https://jsonjoy.com)
Here are the different areas I'm interested in :
1. **Type**: Is it state-based or operation-based? What is the impact for us?
2. **Offline Sync**: How does it handle offline edits and their integration upon reconnection?
3. **Efficiency**: Probe the bandwidth and storage demands. What's being transmitted over the wire? To test that, I've just connected two peers and added 20 points on each, and looked at the network impact.
4. **Community and Support**: How is the size and activity of the developer community / ecosystem?
5. **Size** of the JavaScript library
6. **Browser support** in general.
### Different types of CRDTs
While reading the literature, I found that there are two kinds of CRDTs: state-based and operation-based. So, what do we need ?
It turns out most of the CRDTs implementation I looked at are operation-based, and propose an
API to interact with them as you're changing the state, so **it doesn't really matter**.
> The two alternatives are theoretically equivalent, as each can emulate the
> other. However, there are practical differences. State-based CRDTs are
> often simpler to design and to implement; their only requirement from the
> communication substrate is some kind of gossip protocol. **Their drawback is that
> the entire state of every CRDT must be transmitted eventually to every other
> replica, which may be costly**. In contrast, operation-based CRDTs transmit only
> the update operations, which are typically small.
>
> [Wikipedia on CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type)
### Offline support
One thing that I would like to clarify is how does these libs work when peers get
offline, and back online, if they do something at all.
I was expecting something in the lines of:
1. Connection is lost: changes are applied locally (messages are piling up somehow);
2. Connection is back: the client syncs with other clients.
Which is exactly what's happening: CRDTs don't do any magic here: they send the "sync" messages whenever they need, which brings all the clients in the same state.
### How the server fits in the picture
The sync protocol [is well defined and documented](https://www.npmjs.com/package/@automerge/automerge-repo-network-websocket). While discussing with the team, I quickly understood that I was expecting the server to pass along the messages to the other parties, and that would be the way the synchronisation would be done. It turns out I was mistaken: in this approach, the clients send updates to the server, which merges everything together and only then sends the updates to the other peers.
In order to have peers working with each other, I would need to change the way the provider works, so we can have the server be "brainless" and just relay the messages.
For automerge, it would mean the provider will "just" handle the websocket connection (disconnect and reconnect) and all the peers would be able to talk with each other. The other solution for us would be to have the merge algorithm working on the server side, which comes with upsides (no need to find when the document should be saved by the client to the server) and downsides (it takes some cpu and memory to run the CRDTs on the server)
### Versionning
A quick note about versioning: it's possible to get a snapshot of the document at any point in time, and it's possible to store this information.
## Y.js
Y.js is the first library I've looked at, because it's the oldest one, and the more commonly referred to.
The API seem to offer what we look for, and provides a way to [observe changes](https://docs.yjs.dev/api/shared-types/y.map#observing-changes-y.mapevent).
```js
const doc = new Y.Doc()
const map = ydoc.getMap()
map.set('key', 'value')
map.observe((event) => {
// read the keys that changed
event.keysChanged
// If I need to iterate on the keys, or get the old values, it's possible.
event.changes.keys.forEach((change, key) {
// here, change.action can be "add", "update" or "delete"
// So it's the right place to know what happened, and act respectively
// You can get the last value of the key with `map.get`
map.get(key)
})
})
```
It comes with multiple "providers", which make it possible to sync with different protocols (there is even a way to sync over the matrix protocol 😇). More usefully for us, there is [an implemented protocol for websockets](https://github.com/yjs/y-websocket).
Using a provider is as easy as:
```js
// Sync clients with the y-websocket provider
const provider = new WebsocketProvider("ws://localhost:1234", "leaflet-sync", doc );
```
It's also possible to send "awareness" information (some state you don't want to persist, like the position of the cursor). It contains some useful meta information, such as the number of connected peers.
```js
map.on("mousemove", ({ latlng }) => {
awareness.setLocalStateField("user", {
cursor: latlng,
});
});
```
I made [a quick proof of concept with Y.js](https://gitlab.com/almet/leaflet-sync/-/tree/yjs) in a few hours flawlessly. It handles offline and reconnects, and exposes awareness information.
### Python
Y.js has been rewritten in rust, with [the Y.rs project](https://github.com/y-crdt/y-crdt), which makes it possible to use with Python (see [Y.py](https://github.com/y-crdt/ypy)) if needed. The project has been implemented quite recently and is currently looking for a maintainer.
### Library size
Size: Y.js is 4,16 Ko, Y-Websocket is 21,14 Ko
The library is currently used in production for large projects such as AFFiNE and Evernote.
### The data being transmitted
In the scenario where all clients connect to a central server, which handle the CRDT locally and then transmits back to other parties, I found that adding 20 points on one client, and then 20 points in another generates ~5 ko of data (~16 bytes per edit).
| Pros | Cons |
| -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| the API was feeling natural to me: it handles plain old JavaScript objects, making it easy to integrate. | It doesn't seem to work well [without a JS bundler](https://github.com/yjs/yjs/issues/325) which could be a problem for us. |
| It seems to be widely used, and the community seems active. | |
| It is [well documented](https://docs.yjs.dev/) | |
| There is awareness support | |
---
## Automerge
[Automerge](https://automerge.org/) is another library to handle CRDTs. Automerge is actually the low level interface, and there is a higher-level interface exposed as [Automerge-repo](https://automerge.org/docs/repositories/). Here is how to use it:
```js
let handle = repo.create()
handle.change(d => { d.key = "value"})
```
When you change the document, you actually call `change` which makes it possible to do the changes in a kind of "transaction function".
You can observe the changes, getting you the whole list of patches:
```js
handle.on("change", ({ doc, patches }) => {
patches.forEach(({ action, path }) => {
// Here I'm only taking action when a value is inserted
// At the position "uuid".
if (path.length == 2 && action === "insert") {
let value = doc[path[0]];
// do something here with the value
}
});
```
There is a high-level API, with support for [Websocket](https://www.npmjs.com/package/@automerge/automerge-repo-network-websocket):
```js
import { Repo } from "@automerge/automerge-repo";
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb";
const repo = new Repo({
network: [new BrowserWebSocketClientAdapter("wss://sync.automerge.org")],
storage: new IndexedDBStorageAdapter(),
});
```
### Python
There is an [automerge.py](https://github.com/automerge/automerge-py) project, but no changes has been made to it since 3 years ago.
### Library size
Size: 1,64 mb, total is 1,74 mb. It's relying on Web assembly by default.
The large bundle size is something that the team is aware of, and are working on a solution for. For us, it's important to have something as lightweight as possible, considering CRDTs is only one part of what we're doing, and that mapping can be done in context where connection is not that reliable and fast.
### The data being transmitted
In the same scenario, I found that adding 20 points on one client, and then 20 points in another generates 90 messages and 24,94 Ko of data transmitted (~12 Ko sent and ~12Ko received), so approximately 75 bytes per edit.
| Pros | Cons |
| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| There is an API to [get informed when a conflict occured](https://automerge.org/docs/documents/conflicts/)<br><br> | Documentation was a bit hard to understand and to look at. Sometimes, it's easier to go look at the code.<br><br> |
| In general, the documentation is low level, which can be a good thing while debuging, or when getting more advanced usage. | The API is more verbose. You can see it as "less magical". |
| The team was responsive and trying to help. | There is no way (at the moment) to tell that a transaction is local or remote (but in practice it wasn't a problem) |
---
## JSON Joy
[Json Joy](https://jsonjoy.com) is the latest to the party. It takes another stake at this by providing small libraries with a small functional perimeter. It sounds promising, even if still quite new, and would left us with the hands free in order to implement the protocol that would work for us.
```js
import {Model} from 'json-joy/es2020/json-crdt';
import { s } from "json-joy/es6/json-crdt-patch";
import { encode, decode } from "json-joy/es6/json-crdt-patch/codec/verbose";
// Create a new JSON CRDT document.
const model = Model.withLogicalClock();
const modelMarkers =
// Find "obj" object node at path [].
model.api.root({
markers: {},
});
model.api.obj(["markers", uuid]).set(s.con(target._latlng));
model.api.obj("markers").events.onViewChanges.listen((changes) => console.log(changes))
```
When receiving an update, you could apply it, like this:
```js
let patch = decode(payload);
model.api.apply(patch);
// And see the model with
model.api.view();
```
Metrics:
- Size: 143 ko
- Data transmitted for 2 peers and 40 edits: (35 bytes per edit)
| Pros | Cons |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| It's low level, so you know what you're doing | It doesn't provide a high level interface for sync<br><br> |
| It's separated as different small atomic libraries, which makes it easy to switch bits of the code if needed, without throwing everything away. | Seems to be a one-person project until now |
| The interface [proposes to store different type of data](https://jsonjoy.com/libs/json-joy-js/json-crdt/guide/node-types) (constants, values, arrays, etc) | Quite recent, so probably rough spots are to be found |
| Distributed as different type of JS bundles (modules, wasm, etc) | I didn't find a lot of support, (probably because the project is still quite new) |
## Summarizing CRDT Libraries for uMap
Let's summarize the key considerations and how each CRDT library aligns with our objectives for uMap. The goal is to assess their fit in terms of specific collaborative features, efficiency, and ease of integration.
| Feature / Library | Y.js | Automerge | JSON Joy |
| ------------------------- | ------------------------------------------------------ | ---------------------------------------------------------- | --------------------------------------------------------------- |
| Intuitive API | Provides a natural feel with native JS objects. | API is transactional, detailed for change tracking. | Low-level API offers granular control. |
| Synchronization Protocol | Multiple options with providers, including WebSockets. | Multiple options with providers, including WebSockets. | Requires custom implementation for sync. |
| Conflict Resolution | Automatic merging with Y.js's internal mechanisms. | Detailed conflict detection and resolution API. | Allows control over data types and operations. |
| Offline Changes Handling | Inbuilt support for offline edits and synchronization. | Requires a more manual approach to handle offline changes. | Focused on model updates without specifics on network handling. |
| Versioning and History | Supports selective versioning through snapshots. | Designed with robust version history tracking. | Model is versioned but favors compact storage. |
| Community and Support | Active community with regular updates. | Strong support with a focus on collaboration. | Smaller community; promising but less established. |
| Library Size / Efficiency | Small size with efficient operation. | Larger library with dependency on WebAssembly. | Modular design with compact size. |
| Browser Compatibility | Broad compatibility, some bundler dependencies. | Supports modern browsers with potential polyfills. | Flexible bundles for diverse browser support. |
| Suitability for uMap | Ready-to-use with good documentation and examples. | Strong features, may require significant integration. | Promising, would need robustness as it matures. |
## Notes on YATA and RGA
While researching, I found that the two popular CRDTs implementation out there use different approaches for the virtual counter:
> - **RGA** [used by Automerge] maintains a single globally incremented counter (which can be ordinary integer value), that's updated anytime we detect that remote insert has an id
> with sequence number higher that local counter. Therefore every time, we produce
> a new insert operation, we give it a highest counter value known at the time.
> - **YATA** [used by Yjs] also uses a single integer value, however unlike in case of RGA we
> don't use a single counter shared with other replicas, but rather let each
> peer keep its own, which is incremented monotonically only by that peer. Since
> increments are monotonic, we can also use them to detect missing operations eg.
> updates marked as A:1 and A:3 imply, that there must be another (potentially
> missing) update A:2.
### Resources
- [Bartosz Sypytkowski](https://www.bartoszsypytkowski.com/the-state-of-a-state-based-crdts/) introduction on CRDTs, with practical exemples is very intuitive.
- [CRDT Implementations](https://jzhao.xyz/thoughts/CRDT-Implementations#replicated-growable-array-rga)
- [CRDTs: The Hard Parts - YouTube](https://www.youtube.com/watch?v=x7drE24geUw)
- [An Interactive Intro to CRDTs](https://jakelazaroff.com/words/an-interactive-intro-to-crdts/) gets you trough different steps to understand what are CRDTs, and how to implement a LWW Register.

View file

@ -6,19 +6,19 @@ Perdre pied. Vertige, écroulement et densité. Bruit, solidité, bloc. On s'y p
Laisser la place, sauvage mais fragile. Une **Île**.
La place, pour qu'on s'enlasse il en faut beaucoup. Sans sans lasser, de ça. Là, c'est là, juste devant moi. Et de la place on en fait, comme on en fait plus.
La place, pour qu'on s'enlace il en faut beaucoup. Sans sans lasser, de ça. Là, c'est là, juste devant moi. Et de la place on en fait, comme on en fait plus.
**Nous**, au delà de l'eternel toi et moi d'ailleurs. On s'en abreuve, d'ailleurs. Ce toi qui laisse la place pour nourrir les amitiées. Pour les voir pousser et les laisser déborder.
**Nous**, au-delà de l'éternel toi et moi d'ailleurs. On s'en abreuve, d'ailleurs. Ce toi qui laisses la place pour nourrir les amitiés. Pour les voir pousser et les laisser déborder.
**Restera** des pauses, de la place, et une glace dans laquelle se regarder. Vraiment, en détaillant les traits. Regarder comment ça réfléchit, et parfois donner à voir à ces amitiés. Pour qu'elles en restent. **Ça** c'est ce qu'il nous restera.
Les injonctions, qu'elles en restent là. Ici, c'est dans vos bras quand vous êtes dans les miens. Quand on voit au delà de tout ça.
Les injonctions, qu'elles en restent là. Ici, c'est dans vos bras quand vous êtes dans les miens. Quand on voit au-delà de tout ça.
Des matinées, des silences et de la densité. Le temps passe, passera. Qui occupe qui ? à quoi, et pourquoi?
Des matinées, des silences et de la densité. Le temps passe, passera. Qui occupe qui ? à quoi, et pourquoi?
Écouter. Oiseaux, ballade et soleil. Silence, fluidité, ouverture. On s'y retrouverait.
Il nous restera ça.
---
Contraintes: utiliser « il nous restera ça », 30mn.
Contraintes : utiliser « il nous restera ça », 30mn.

View file

@ -12,13 +12,13 @@ Jusqu'à retrouver ce qui fait geste. Une envie de lenteur, d'un autre rapport
Le murmure s'amplifie, en aller-retours — des vagues presque — et j'aime le contraste que ça me donne. La mise à distance que ça crée.
Je vois les choses se détailler. Une rupture ou un décalage, une autre cynetique, une évolution.
Je vois les choses se détailler. Une rupture ou un décalage, une autre cinétique, une évolution.
Les vagues viennent à la rencontre des rochers, en essayant de briser leur inertie. L'eau les érode, et je me sens vivant.
« Ce qui me manque c'est l'élan ! »
« Ce qui me manque, c'est l'élan ! »
Je me lève et je me mets en mouvement. Sortir de mes projections — de mon cinéma presque — m'est étonnement facile, comme si j'avais trouvé un trait d'union, et retrouvé mon souffle.
Je me lève et je me mets en mouvement. Sortir de mes projections — de mon cinéma presque — m'est étonnement facile, comme si j'avais trouvé un trait d'union et retrouvé mon souffle.
---

View file

@ -2,17 +2,17 @@
title: Sapins
---
C'est d'abord son odeur qui m'arrive aux narines, une effluve de résine, un côté fruité, presque épicé. Une puissance aromatique qui vient en plusieurs temps, d'abord me chatouiller les narines, puis remplir mon espace olfactif jusqu'à ce que je ne sentes plus rien d'autre. Entêtant.
C'est d'abord son odeur qui m'arrive aux narines, une effluve de résine, un côté fruité, presque épicé. Une puissance aromatique qui vient en plusieurs temps, d'abord me chatouiller les narines, puis remplir mon espace olfactif jusqu'à ce que je ne sente plus rien d'autre. Entêtant.
Je baisse la tête et je me rends compte que la végétation à disparue. Au delà des épines de pin qui jonchent le sol j'ai du mal à distinguer autre chose, comme si la place était prise, mais pas de manière visible. Comme si un code invisible était de rigueur, et avait pour effet de tenir les autres à l'écart.
Je baisse la tête et je me rends compte que la végétation à disparue. Au-delà des épines de pin qui jonchent le sol, j'ai du mal à distinguer autre chose, comme si la place était prise, mais pas de manière visible. Comme si un code invisible était de rigueur, et avait pour effet de tenir les autres à l'écart.
Ces épines donc, longues et fines, trapues presque. Ternes et sèches, souvent groupées par deux. Je me demande pourquoi ? Pour s'assurer qu'une des deux arrive à ses fins ? Pour s'accompagner ?
Le sol est aride, mes pas s'espacent, s'agrandissent. Partout autour de moi c'est le même paysage. Beau et désolant à la foi, par son unicité.
Le sol est aride, mes pas s'espacent, s'agrandissent. Partout autour de moi, c'est le même paysage. Beau et désolant à la foi, par son unicité.
Je me retourne pour contempler, ou plutôt j'essaye sans y parvenir. Je suis attiré par un espace vide un peu plus loin, sans trop comprendre ce que j'y trouve. Je me demande encore une fois ce que je fais là, au milieu de cette forêt de sapin, à 500km de chez moi. Il fait froid alors qu'on est en plein été. Le vent me chatouille et me guide, je me demande si c'est lui qui m'a conduit ici.
Je me retourne pour contempler, ou plutôt j'essaye sans y parvenir. Je suis attiré par un espace vide un peu plus loin, sans trop comprendre ce que j'y trouve. Je me demande encore une fois ce que je fais là, au milieu de cette forêt de sapin, à 500 km de chez moi. Il fait froid alors qu'on est en plein été. Le vent me chatouille et me guide, je me demande si c'est lui qui m'a conduit ici.
J'arrive à l'orée de cet espace vide que j'avais vu, qui est en fait une petite clairière. L'espace que je croyais vide est en fait rempli de mille détails, fourmillants. J'observe le sol et me rends compte qu'il est doux, accueillant. J'ai du mal à m'expliquer pourquoi, et je passe outre. Finalement, l'important c'est la sensation. Je m'allonge et mes épaules se relâchent. Je sens la tension accumulée se décharger dans le sol. Je sursaute de repos, et mon cerveau se lâche. Me lâche. Mais dans le bon sens du terme, il me laisse tranquille, le temps de ces quelques minutes de repos. Je ne sais pas combien de minutes — ou d'heures — s'écoulent mais c'est délicieux. Je sens mes sens rentrer en éveil et me dire que la nuit arrive bientôt. La douce chaleur du soleil, celle qui me caressait les épaules s'en est en allée et par la même m'a chuchoté de m'en aller.
J'arrive à l'orée de cet espace vide que j'avais vu, qui est en fait une petite clairière. L'espace que je croyais vide est en fait rempli de mille détails, fourmillants. J'observe le sol et me rends compte qu'il est doux, accueillant. J'ai du mal à m'expliquer pourquoi, et je passe outre. Finalement, l'important c'est la sensation. Je m'allonge et mes épaules se relâchent. Je sens la tension accumulée se décharger dans le sol. Je sursaute de repos, et mon cerveau se lâche. Me lâche. Mais dans le bon sens du terme, il me laisse tranquille, le temps de ces quelques minutes de repos. Je ne sais pas combien de minutes — ou d'heures — s'écoulent mais c'est délicieux. Je sens mes sens rentrer en éveil et me dire que la nuit arrive bientôt. La douce chaleur du soleil, celle qui me caressait les épaules s'en est allée et par là même m'a chuchoté de m'en aller.
Je me remets en marche, remets mon gilet sur mes frêles épaules et je me trouve jolie. Légère en fait. Je dois affronter cette forêt de sapin qui vu d'ici me parait hostile. En m'approchant je ressens l'épinosité et mes sens qui s'agitent. Ça se passera bien, vite et bien. Je traverse d'un coup de vent cette forêt et finalement assez bien, oui.

View file

@ -4,7 +4,7 @@ title: Souvenir du thé
Le paysage file. Mon wagon, accroché à tous les autres me propulse dans l'espace, avec calme et sérénité. Je baille.
De manière assez étrange, cette vitesse me semble ralentir le temps, créer un espace en lui même, suspendu.
De manière assez étrange, cette vitesse me semble ralentir le temps, créer un espace en lui-même, suspendu.
En me perdant dans mes pensées, je saisis le magazine acheté devant la Gare d'Amiens en partant, et le feuillette, espérant y trouver un quelconque intérêt. Peine perdue, autant chercher une aiguille dans une botte de foin.
@ -12,9 +12,9 @@ En me perdant dans mes pensées, je saisis le magazine acheté devant la Gare d'
C'est d'abord les effluves de torréfaction qui me parviennent. En approchant les odeurs se détaillent, et au torréfié s'ajoute le fleuri, l'épicé. Le végétal et l'acidité.
Une fille assez grande s'approche de moi, elle à une écharpe autour du cou et est habillée très coloré, assez élégante. « Comment allez vous aujourd'hui ? ». Je ne crois pas la connaitre et pourtant son ton ne laisse pas de place pour le doute : elle, me connait.
Une fille assez grande s'approche de moi, elle à une écharpe autour du cou et est habillée très coloré, assez élégante. « Comment allez-vous aujourd'hui ? ». Je ne crois pas la connaitre et pourtant son ton ne laisse pas de place pour le doute : elle, me connait.
Elle saisit une tasse derrière elle et la rempli avant de me la tendre, sans même me demander. Le liquide chaud fait des volutes de vapeur qui tourbillonnent avant de se dissiper dans la pièce. Le soleil qui vient de se lever ajoute à l'atmosphère du moment. Je trouve ça beau.
Elle saisit une tasse derrière elle et la remplie avant de me la tendre, sans même me demander. Le liquide chaud fait des volutes de vapeur qui tourbillonnent avant de se dissiper dans la pièce. Le soleil qui vient de se lever ajoute à l'atmosphère du moment. Je trouve ça beau.
Je bois, et laisse mes sens errer à leurs découvertes. Un objet posé sur la petite table qui nous sépare attire mon attention. Rond, chaleureux, ample et rempli de ce fameux liquide. Les motifs qui l'ornent me rappellent vaguement quelque chose, mais quoi ?

View file

@ -14,11 +14,11 @@ et les laisser fleurir, qui sait ?
Qui dit veine dit chance.
Qui dit jeu dit dès.
Si je m'efface alors qui suis-je ?
Si je m'efface, alors qui suis-je ?
Si je fais face, le jeu refait surface.
Qui dit quoi ? Dis moi ?
Qui t'envoie ? Dis moi !
Qui dit quoi ? Dis-moi ?
Qui t'envoie ? Dis-moi !
Qui dit spirale dit certitudes.
@ -33,7 +33,7 @@ pousser, puis fleurir, l'été.
---
Contraintes:
Contraintes :
- ⏲ 15mn
- Sappuyer sur la structure « Qui dit… dit… » (répété autant de fois que voulu) et conclure par « Alors… ».

View file

@ -11,25 +11,25 @@ title: Les vacances avaient commencées
---
Le trajet de quasiment trois heures en ligne droite était agréable. Sans être profondes, les discussions étaient enjouées et on sentait que le printemps colorait nos émotions. Je ressentais une légèreté, une envie, une poussée. Je voulais faire exactement ce que je faisais. Et là c'était discuter avec Romain.
Le trajet de quasiment trois heures en ligne droite était agréable. Sans être profondes, les discussions étaient enjouées et on sentait que le printemps colorait nos émotions. Je ressentais une légèreté, une envie, une poussée. Je voulais faire exactement ce que je faisais. Et là, c'était discuter avec Romain.
La parole avait doucement cédé la place à la musique qui avait finalement ampli l'espace sonore. Comme une évidence. L'énergie brute qui nous emportait nous avait presque fait oublier que nous arrivions. On dansait sur nos sièges.
On s'était garés côté jardin, c'était plus pratique pour décharger notre cargaison. Ah, on ne savait plus voyager léger ! Entre la contrebasse, le vidéo-projecteur et les réserves pour la semaine, on peut dire qu'on était parés.
La porte de côté était ouverte, c'était donc évident qu'il se trouvait quelqu'un à l'intérieur et que nous n'aurions pas à regarder sous le pot de fleurs pour récupérer les clés comme nous l'avait précisé Manon. Je me demandais qui était déjà là ?
La porte de côté était ouverte, c'était donc évident qu'il se trouvait quelqu'un à l'intérieur et que nous n'aurions pas à regarder sous le pot de fleurs pour récupérer les clés comme nous l'avait précisé Manon. Je me demandais qui était déjà là ?
« Ouiiiiiii ! Vous êtes arrivés !! Les chouchous, comment ça va ? La route était bonne ? Ah, ça me fait tellement plaisir de vous voir ! »
Ah ! Pierre ! Fidèle à lui même, finalement. Ça faisait plaisir de le voir de bonne humeur après le sale quart d'heure qu'il avait passé cet hiver.
Ah ! Pierre ! Fidèle à lui-même, finalement. Ça faisait plaisir de le voir de bonne humeur après le sale quart d'heure qu'il avait passé cet hiver.
« Attendez … posez vous cinq minutes. On va prendre un petit thé pour vous faire redescendre de votre vitesse de croisière, question d'arriver tranquillement en vacances.
« Attendez… posez-vous cinq minutes. On va prendre un petit thé pour vous faire redescendre de votre vitesse de croisière, question d'arriver tranquillement en vacances.
Vous avez faim ? J'ai préparé des petits crackers maison en attendant que le reste du *crew* arrive. Vous me connaissez, j'ai du mal à me poser quand je peux cuisiner.
Enfin j'ai dit un thé, mais si vous préférez autre chose… Faites comme chez vous hein. Enfin je dis ça, mais c'est pas plus chez moi que chez vous.
C'est quand même génial que la grand mère de Nora nous prête sa maison. Le pied ! »
C'est quand même génial que la grand-mère de Nora nous prête sa maison. Le pied ! »
Ah oui, il était pas complètement redescendu non plus le Pierrot, vu son débit de paroles.
@ -39,13 +39,13 @@ On embarquait quelques bières (après-tout pourquoi pas, c'était les vacances
On avait fini par se baigner, faisant des aller-retour dans la mer tiède dès que le soleil nous chauffait trop, alternant entre jeux, histoires et discussions. Un rare moment à trois, un excellent moyen de se retrouver.
Juste avant que le soleil se couche on avait aperçu les silhouettes de Manon, Jeanne et Marc se détailler en contre plongée sur le rocher qui permettait d'accéder à la plage le petit mot laissé sur la table de la cuisine avait fait son petit effet, et le *crew* était au complet.
Juste avant que le soleil se couche on avait aperçu les silhouettes de Manon, Jeanne et Marc se détailler en contre-plongée sur le rocher qui permettait d'accéder à la plage le petit mot laissé sur la table de la cuisine avait fait son petit effet, et le *crew* était au complet.
Les vacances avaient commencées.
---
Contraintes:
Contraintes :
- ⏲ 45mn
- ⛓ Utiliser la phrase « La porte de côté était ouverte, c'était donc évident qu'il se trouvait quelqu'un à l'intérieur et que nous n'aurions pas à regarder sous le pot de fleurs. »

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View file

@ -4,6 +4,39 @@ save_as: umap/index.html
template: worklog
total_days: 25
---
## Mardi 12 Mars 2024 (7h, 5/5)
Une bonne journée, passée majoritairement en pair prog avec Yohan. On a d'abord fait un point de synchro sur l'avancement général de la synchro, durant lequel on a plus ou moins décidé d'aller dans un premier temps vers la version simple de la synchronisation, en faisant en sorte que les clients écrasent les données des autres clients, avec le serveur qui s'occupe de faire le passe-plat.
Ça laisse les CRDTs de côté pour le moment, l'objectif devenant donc d'avoir quelque chose de fonctionnel derrière un feature flag pour les gens qui ont envie de tester, au risque de renverser le café au milieu de la table. On verra ensuite en quoi les CRDTs sont utiles, si c'est le cas.
Ensuite, je suis donc reparti sur ce que j'avais laissé de côté hier, à savoir le fait d'avoir des données en plus expliquées dans le schema, pour savoir ce qui va se rerendre. L'idée avec le schéma étant d'avoir quelque chose d'abstrait, on est parti sur une clé `impacts` qui permet de lister ce que chaque propriété impacte: l'ui, les données, etc.
On a déroulé ce fil là ensuite, en repassant sur chacune des propriétés et en ajoutant celles qui manquaient. Un peu fastidieux, mais ça me donne une meilleure compréhension de certains bouts du code.
Je me suis arrêté au moment ou on commençait à voir en quoi certaines propriétés des layers nécessitaient un comportement spécifique (par exemple le choropleth), affaire à suivre !
## Lundi 11 Mars 2024 (8h, 5/5)
J'ai fait un tour des pull requests en cours le matin, c'était chouette d'avoir un peu de bande passante pour faire le tour de ce qui avait été proposé et qui était en attente. J'ai ensuite continué la comparaison des différentes bibliothèques de CRDT, en cherchant à comprendre quelle était l'utilisation réseau.
Une conversation avec Alex CC sur le discord d'Automerge m'a permis de mieux comprendre que le modèle implémenté par automerge-repo-websockets était en fait un modèle ou le serveur centralise (et donc fait tourner le CRDT) les flux, alors que je pensais que c'était les clients qui se relayaient les messages.
On se fait un petit point de synchro avec David, ça faisait longtemps, c'était bien ! Je creuse sur les différentes manières d'implementer le flux de données, et de manière sur l'approche à prendre autour de cette synchronisation.
Ça me pousse à questionner le choix du décentralisé: je me demande si il est réellement souhaitable, et ce qu'il nous apporterait, concrètement, vis à vis d'une approche plus traditionnelle avec le serveur au centre. Il va falloir trancher pour avancer.
Weekly, durant laquelle on me demande de justifier les heures facturées qui étaient prévues mais un peu flottantes. Ça me poussera à être plus clair dans le futur.
Après la weekly, je commence à implémenter les `propertyRenderer`, en essayant de les intégrer avec le nouveau concept de "schema" qui a été mergé recemment. L'idée étant d'avoir un seul endroit ou les propriétés sont définies, ainsi que leur comportement. Les propriétés pouvant appartenir à différents contextes (la carte, les layers, les features) il faut trouver comment représenter ce contexte sans trop ajouter de complexité, mais en restant flexible pour le futur. On part sur une clé `renderers` qui est un objet avec en clé les différents contextes:
```js
renderers: {
map: ['list', 'of', 'renderers']
features: ['list', 'of', 'renderers']
}
```
Au passage, je me rends compte qu'il est possible de grandement simplifier le code qui s'occupe d'appeler les renderers, pour le mettre dans une fonction (à la python) plutôt que dans une classe. Après tout, il n'y a pas besoin d'avoir tout le contexte de la classe, uniquement de pouvoir appeler les méthodes pour se re-rendre.
## Vendredi 8 Mars 2024 (7h, 5/5)
J'ai refais une passe rapide sur les PR en cours d'intégration, et j'en ai profité pour m'assurer que le merge des features entre les anciennes versions (sans ids) et les nouvelles (avec ids) vont pouvoir fonctionner. Je suis content de voir que c'était déjà prévu dans le code d'origine, ouf, une chose de moins à se soucier.

View file

@ -10,7 +10,7 @@ De retour après trois semaines off qui m'ont fait du bien, passées entre ami·
## Ce qui s'est passé
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
3 jours de travail et de discussions avec Yohan sur le projet. J'ai pu lever des freins à mon avancée, et je crois à notre compréhension mutuelle. Des discussions qui nous permettent de mieux se comprendre, et de sortir de nos postures.
@ -33,4 +33,4 @@ En fin de semaine, je me suis senti plus à l'aise à travailler sur la base de
## Vu, lu, écouté
- 📼 Beaucoup de conf lors du 37C3, entre autres [des gens de Kaspersky qui ont réussi à prendre des hackers sur le fait](https://media.ccc.de/v/37c3-11859-operation_triangulation_what_you_get_when_attack_iphones_of_researchers), une [revue de l'année passée en terme de surveillance par LQDN](https://media.ccc.de/v/37c3-12309-a_year_of_surveillance_in_france_a_short_satirical_tale_by_la_quadrature_du_net), un [retour sur la censure du réseau Tor en russie](https://media.ccc.de/v/37c3-12040-tor_censorship_attempts_in_russia_iran_turkmenistan), une [investigation d'Amnesty International autour de l'affaire « Predator »](https://media.ccc.de/v/37c3-12168-predator_files_how_european_spyware_threatens_civil_society_around_the_world) et un retour sur [la censure et l'instauration d'un régime autoritaure en Bielorussie](https://media.ccc.de/v/37c3-11836-tractors_rockets_and_the_internet_in_belarus)
- 🎧 J'ai continué à écouter [Darknet diaries](https://darknetdiaries.com/), avec des discussions autour du logiciel espion [Predator](https://darknetdiaries.com/episode/137/), des [communautés qui volent du bitcoin](https://darknetdiaries.com/episode/112/), et d'[affaires d'espionage des USA sur la grèce autour des jeux olympiques](https://darknetdiaries.com/episode/64/).
- 🎧 J'ai continué à écouter [Darknet diaries](https://darknetdiaries.com/), avec des discussions autour du logiciel espion [Predator](https://darknetdiaries.com/episode/137/), des [communautés qui volent du bitcoin](https://darknetdiaries.com/episode/112/), et d'[affaires d'espionage des USA sur la grèce autour des jeux olympiques](https://darknetdiaries.com/episode/64/).

View file

@ -8,7 +8,7 @@ projects: umap, chariotte
## Ce qui s'est passé
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
Deux petites demi-journées de travail. Des discussions en collectif sur ce qu'on attends de nos réunions hebdo, et une discussion sur les orientations techniques et sur notre manière de prendre des décisions.

View file

@ -8,7 +8,7 @@ projects: umap, chariotte
## Ce qui s'est passé
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
: 9ème semaine sur le projet
: J'ai changé d'approche et je ne cherche pour le moment plus à intégrer des CRDTs dans la base de code actuelle, ce qui me permet de me concentrer sur les aspects métiers.
: J'ai eu un premier bout de démo à base de websockets et d'évènements qui transitent d'un client à l'autre.

View file

@ -10,7 +10,7 @@ projects: umap, chariotte, argos, notmyidea
[IDLV.co](https://idlv.co)
: Le site web est maintenant public !
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
: 10ème semaine, on dépasse les 200h.
: J'ai fais une session de travail « en ermite », et j'ai trouvé ça utile pour avancer.
: J'ai [une démo de synchro qui fonctionne](https://files.notmyidea.org/umap-sync-features.webm) !
@ -44,4 +44,4 @@ projects: umap, chariotte, argos, notmyidea
## Notes
> Maybe youve heard of it? Its called HTML, and it works with [every Javascript framework](https://custom-elements-everywhere.com). For all their warts, the fact that web components get this interoperability for free is a _ridiculously powerful advantage_, and libraries that dont exploit it are leaving a lot of potential users on the table.
> — https://jakelazaroff.com/words/the-web-component-success-story/
> — https://jakelazaroff.com/words/the-web-component-success-story/

View file

@ -8,7 +8,7 @@ projects: umap, chariotte
Une bonne semaine assez sympathique, entre Angers et Rennes. Je récupère de l'énergie (encore, c'est dingue) et je réussis à faire des avancées en terme de motivation générale.
## Ce qui s'est passé
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
: On à commencé à clarifier certains aspects économiques du projet lors d'une discussion avec David et Yohan. C'est chouette que ça se fasse même si je ne suis pas encore tout à fait à l'aise sur le sujet.
: On a fait une rétrospective d'équipe avec tout le monde. Ça aurait pu être mieux, mais ça à quand même fluidifié certains trucs chez moi.
: J'ai continué à progresser sur les aspects pratiques de la synchronisation. J'arrive maintenant à synchroniser différents types de points (`Marker`, `Polyline`, `Polygon`) ainsi que les données associées.
@ -44,4 +44,4 @@ Une bonne semaine assez sympathique, entre Angers et Rennes. Je récupère de l'
## Notes
- Un article de MDN sur comment écrire de la doc: https://developer.mozilla.org/en-US/blog/technical-writing/
- Un article de MDN sur comment écrire de la doc: https://developer.mozilla.org/en-US/blog/technical-writing/

View file

@ -8,7 +8,7 @@ projects: umap
Un super début de semaine, une perte d'énergie puis du temps pour moi et de la lecture. Content de voir le chemin parcouru sur uMap depuis que j'ai commencé.
## Ce qui s'est passé
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
: J'ai pu aller au bout de mon expérimentation sur la synchronisation des propriétés de la carte, des géométries et des `layers`. Je suis plutôt content de la simplicité finale de la proposition, même si bien sur beaucoup de cas limites ne sont pas couverts.
: J'ai écrit [un article technique pour suivre mes avancées](https://blog.notmyidea.org/adding-collaboration-on-umap-third-update.html)
## Des joies 🤗
@ -39,4 +39,4 @@ Un super début de semaine, une perte d'énergie puis du temps pour moi et de la
## Notes
Je ne m'étais pas rendu compte que je passais mon temps entre de la gestion de conflits humains, et la gestion des conflits dans la sphère technique, ça me donne envie de creuser le parallèle.
Je ne m'étais pas rendu compte que je passais mon temps entre de la gestion de conflits humains, et la gestion des conflits dans la sphère technique, ça me donne envie de creuser le parallèle.

View file

@ -7,7 +7,7 @@ projects: umap
## Ce qui s'est passé
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
: J'ai terminé mon expérimentation sur la synchronisation, ça fonctionne en mode « démo » et ça me donne une compréhension du code suffisante pour avancer sur le sujet.
: J'ai commencé à discuter avec Aurélie de comment faire pour la partie expérience utilisateur·ice. C'est chouette parce que je connais mal cette partie. Hâte d'avancer la dessus !
: J'ai proposé quelques modifications pour ajouter des identifiants uniques, [pour chaque feature](https://github.com/umap-project/umap/pull/1649), et [pour chaque layer](https://github.com/umap-project/umap/pull/1630).

View file

@ -1,4 +1,3 @@
---
date: 2024-03-05
headline: notes hebdo de la semaine
projects: umap
@ -7,7 +6,7 @@ projects: umap
## Ce qui s'est passé
**🗺️ [uMap](https://umap-projet.org)**
**🗺️ [uMap](https://umap-project.org)**
: Une semaine à consolider ce qui à été entamé la semaine précédente.
: Un correctif sur la fusion de données sur le serveur. [Le protocole que l'on utilisait](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified) avait une résolution à la seconde, ce qui faisait que le serveur perdait les pédales. Ça m'a permis de voir d'un peu plus près certains aspects du code, et comment étaient générés les identifiants de version.
: Au passage, ça m'a permis de corriger un bug qui ne nous avait pas encore été rapporté.

59
content/weeknotes/20.md Normal file
View file

@ -0,0 +1,59 @@
---
date: 2024-03-12
headline: notes hebdo de la semaine
projects: umap
---
# Notes hebdo #20
Une semaine à Barcelone avec [Rémy](https://github.com/Natim) et [Mathieu](https://blog.mathieu-leplatre.info/), partagée entre des moments de boulot et des moments plus relax.
## Ce qui s'est passé
**🗺️ [uMap](https://umap-project.org)**
: J'ai refait une passe rapide sur les PR en cours d'intégration, et j'en ai profité pour m'assurer que le merge des features entre les anciennes versions (sans ids) et les nouvelles (avec ids) vont pouvoir fonctionner. Je suis content de voir que c'était déjà prévu dans le code d'origine, ouf, une chose de moins à se soucier.
: J'ai travaillé sur un article de comparaison sur les bibliothèques de CRDTs (à paraitre). Une partie du travail était de tester les bibliothèques en question (yjs, automerge, jsonjoy) pour synchroniser une carte Leaflet. Pas mal d'aspects intéressants, à la fois sur la dynamique de chaque communauté, mais aussi sur le fonctionnement par défaut qui est prévu par les adaptateurs Websockets, qui s'attendent à ce qu'un merge ait lieu coté serveur (alors que je m'attendais à ce que ce soit fait côté client).
## Des joies 🤗
- 🤗 Passer des moments à discuter et à échanger avec des anciens collègues, et voir que c'est fluide et qu'on à beaucoup apprécié travailler les uns avec les autres. Ça fait du bien !
- 🫣 Recevoir la confirmation qu'une de mes intuitions n'était pas bonne, sur des aspects techniques. Me rendre compte que j'étais la personne qui empêchait d'avancer, et lever les freins en question.
- 🧘🏼 Apprécier me ressourcer lors de plusieurs moments seuls dans cette semaine collective. J'en ai senti les bénéfices immédiats.
- 🙌 Me laisser porter, être hébergé dans le centre ville ouvrant une simplicité d'organisation.
- 🥹 Recevoir un mail de remerciement, ça m'a touché, il faut que je prenne le temps de répondre.
## Des peines 😬
- 💥 Je me suis senti hors de ma zone de confort à travailler de manière décousue sur la semaine. J'aurais préféré travailler à des moments spécifiques et être complètement off à d'autres, pour maximiser le confort dans les deux cas. Là, c'était toujours un peu flottant. J'aimerais le prévoir pour mes prochains déplacements.
- 😮‍💨 Je me suis vu chercher la validation des autres. J'aimerai mieux évaluer mes compétences et limites.
## Vu, Lu, etc
- 🎧 [Proudhon, "La propriété, cest le vol" : épisode • 2/4 du podcast Aux ordres de lanarchie](https://www.radiofrance.fr/franceculture/podcasts/les-chemins-de-la-philosophie/proudhon-la-propriete-c-est-le-vol-7814820), épisode intéressant qui explique que pour Proudon, la propriété est surtout la « propriété industrielle », qui est distincte de la « possession ». Le propriétaire des forces de production récupérant le travail de ceux qui oeuvrent pour lui.
- 🎶 [Monolink (live) - Mayan Warrior - Burning Man 2022 - YouTube](https://www.youtube.com/watch?v=AQURf3JqnJY)
- 🎶 [Parra for Cuva (live) - Mayan Warriror - Burrning Man 2022 - YouTube](https://www.youtube.com/watch?v=7jfxcDudvS8)
- 🎶 [Mira - Mayan Warrior x Robot Heart Link Up - Burning Man 2019 - YouTube](https://www.youtube.com/watch?v=F9sCiyIWvcU)
---
## Décisions
> We use the phrase “making” decisions very intentionally: you do not discover a decision, or calculate it, or measure it, or even arrive at it. [A decision is a thing that you _make._](https://aworkinglibrary.com/writing/making-decisions) Its a creative act. Recognizing that, I think, provides some direction for how to think about creating space for the work of making decisions. Its less akin to making space for completing tasks or attending meetings, and more like making space for art.
>
> — [On making decisions | everything changes](https://everythingchanges.us/blog/making-decisions/)
En français, peut-être qu'on peut trouver les mêmes mécanismes entre l'idée de « prendre une décision », ou de « faire un choix ». Je me rends compte que c'est quelque chose de pas tout à fait résolu pour moi qui laisse souvent les portes ouvertes, qui ait du mal à trancher.
J'aime bien l'idée de « faire » une décision, parce que c'est quelque chose qui peut aussi s'inscrire dans le temps. Contrairement à l'idée qu'une décision devrait se prendre nécessairement rapidement, on peut progresser vers le bon endroit.
---
## Comptes
> With the new version of Signal, **you will no longer broadcast your phone number to everyone you send messages to by default**, though you can choose to if you want.
> [...]
> You also now have the option to set a username, which Signal lets you change whenever you want and delete when you dont want it anymore.
>
> [New Signal Usernames Help Stymie Subpoenas, Data Priavacy](https://theintercept.com/2024/03/04/signal-app-username-phone-number-privacy/)
Depuis le temps que les noms d'utilisateurs étaient attendus pour signal... je suis un peu déçu. Cela ressemble assez fortement à des liens d'invitation qui peuvent être désactivables, avec quelques soucis liés au squatting et au fait de pouvoir réutiliser d'anciens « usernames » qui ne sont plus utilisés.
> If Signal receives a subpoena demanding that they hand over all account data related to a user with a specific username that is currently active at the time that Signal looks it up, they would be able to link it to an account. **That means Signal would turn over that users phone number, along with the account creation date and the last connection date.**
Je m'attendais à ce que les numéros de téléphones ne soient plus requis, mais ce n'est malheureusement pas le cas, et il semble toujours possible de pouvoir lier un username avec un numéro de téléphone pour les gens qui opèrent les serveurs. Mince.
> In short, if youre worried about Signal handing over your phone number to law enforcement based on your username, you should only set a username when you want someone to contact you, and then delete it afterward. And each time, always set a different username.

View file

@ -28,20 +28,19 @@
}
@font-face {
font-family: "Bricolage Grotesque";
src: url("../fonts/BricolageGrotesque96pt-Regular.woff2") format("woff2");
font-family: "Luciole";
src: url("../fonts/Luciole-Regular.ttf") format("ttf");
font-style: normal;
font-weight: 350;
font-stretch: 100%;
font-display: swap;
unicode-range: U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+304, U+308, U+329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
body {
background: var(--main-bg-color);
color: var(--text-color);
font-family: "Bricolage Grotesque", Arial, sans-serif;
background-image: url(/theme/white-waves.webp);
font-family: "Luciole", Arial, sans-serif;
/* background-image: url(/theme/white-waves.webp); */
background-repeat: repeat;
margin-left: auto;
@ -103,7 +102,6 @@ h4 {
}
time {
border-bottom: 1px solid;
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
}
@ -226,9 +224,8 @@ a.tag:hover {
color: var(--highlight-hover);
}
ul,
ol {
padding: .5em 0em 1em 3.2em;
ul {
list-style-type: none;
}
ul li,
@ -255,10 +252,6 @@ pre {
white-space: pre-wrap;
}
header {
padding-bottom: 20px;
}
header a {
text-decoration: none;
}
@ -302,7 +295,6 @@ section.index h1 {
}
#links a {
margin-right: 15px;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 10px;
@ -312,6 +304,10 @@ section.index h1 {
/*text-decoration-thickness: unset;*/
}
#links li a:not(.main){
text-transform: lower-case;
}
#links a:hover {
color: var(--link-color-menu);
}
@ -371,8 +367,8 @@ p code {
footer {
text-align: center;
color: var(--headers-color);
font-size: 13px;
letter-spacing: 4px;
}
@ -472,6 +468,11 @@ a[data-size='5'] {
font-size: 2.2em;
}
#feed {
text-decoration: none;
position: relative;
top: 5px;
}
#feed img {
height: 25px;
width: 25px;
@ -501,3 +502,57 @@ dd {
display: inline;
float: left;
}
.post-title {
margin-bottom: 0px;
}
.items {
padding-left: 0px;
}
.item {
flex-direction: row;
display: flex;
padding-bottom: 0.5em;
}
.page-title {
flex: 0;
}
.item>time {
flex: 1;
text-align: right;
color: #acabab;
padding-left: 1em;
}
#content header{
text-align: center;
}
article {
text-align: justify;
}
nav {
padding-top: 2em;
}
.navigation {
list-style-type: none;
display: inline-block;
padding-left: 0px;
}
.navigation > li {
display: inline;
padding: 0px 0.5em;
}
.navigation > li::after {
content: " ";
letter-spacing: 1em;
background: linear-gradient(90deg, transparent calc(50% - 0.03125em), currentColor 0, currentColor calc(50% + 0.03125em), transparent 0);
}

View file

@ -3,35 +3,56 @@
{% block content %}
<header>
{% if article.category == "Lectures" %}
<div class="book-container">
<div class="book">
<h1 class="post-title">
<a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a><small>
<br />
par {{ article.author }}</small>
</h1>
{% if article.headline %}
<p>
<em>{{ article.headline }}</em>
</p>
{% endif %}
<time datetime="{{ article.date.isoformat() }}">Lu en {{ article.date | strftime("%B %Y") }}</time>
</div>
{% if article.category == "Lectures" and article.isbn_cover %}
<div class="book-cover">
<img src="{{ SITEURL }}/{{ article.isbn_cover }}" />
</div>
<div class="book-container">
<div class="book">
<h1 class="post-title">
<a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a><small>
<br />
par {{ article.author }}</small>
</h1>
{% if article.headline %}
<p>
<em>{{ article.headline }}</em>
</p>
{% endif %}
<time datetime="{{ article.date.isoformat() }}">Lu en {{ article.date | strftime("%B %Y") }}</time>
</div>
{% else %}
<h1 class="post-title">{{ article.title }}</h1>
{% if article.headline %}
<p>
<em>{{ article.headline }}</em>
</p>
{% endif %}
<time datetime="{{ article.date.isoformat() }}">{{ article.locale_date }}</time>
{% if article.category == "Lectures" and article.isbn_cover %}
<div class="book-cover">
<img src="{{ SITEURL }}/{{ article.isbn_cover }}" />
</div>
{% endif %}
</div>
{% else %}
<h1 class="post-title">{{ article.title }}</h1>
<time datetime="{{ article.date.isoformat() }}">{{ article.locale_date }}</time>
<nav>
<ul class="navigation">
{% if article.prev_article %}
<li>
<a href="{{ SITEURL }}/{{ article.prev_article.url }}"
title="{{ article.prev_article.title }}">← Précédent</a>
</li>
{% endif %}
<li>
<a href="{{ SITEURL }}">Accueil</a>
</li>
{% if article.next_article %}
<li>
<a href="{{ SITEURL }}/{{ article.next_article.url }}"
title="{{ article.next_article.title }}">Suivant →</a>
</li>
</ul>
</nav>
{% endif %}
</p>
</nav>
{% if article.headline and article.category != "weeknotes" %}
<p>
<em>{{ article.headline }}</em>
</p>
{% endif %}
{% endif %}
</header>
<article>
{{ article.content }}

View file

@ -7,19 +7,17 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="{{ SITEURL }}/theme/css/{{ CSS_FILE }}?v2"
href="{{ SITEURL }}/theme/css/{{ CSS_FILE }}?v3"
type="text/css" />
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}"
type="application/atom+xml"
rel="alternate"
title="{{ SITENAME }} ATOM Feed" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
{% if FEED_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_RSS }}"
type="application/atom+xml"
rel="alternate"
@ -29,27 +27,24 @@
</head>
<body>
<div id="content">
<section id="links">
<ul>
{% for (title, url, selected) in MENU %}
{% if loop.first %}
<li>
<a class="main" href="/">{{ SITENAME }}</a>
</li>
{% endif %}
{% block content %}{% endblock %}
<hr />
<footer>
<ul id="links">
<li>
<a class="main" href="/">{{ SITENAME }}</a>
</li>
{% for title, link in FOOTERITEMS %}
<li>
<a class="{% if page_name == selected or (category and category.name == selected) or (page and page.slug == selected) %}selected{% endif %}"
href="{{ SITEURL }}{{ url }}">{{ title }}</a>
<a href="{{ link }}">{{ title }}</a>
</li>
{% endfor %}
<li>
<a id="feed" href="/feeds/all.atom.xml">
<img alt="RSS Logo" src="/theme/rss.svg" />
</a>
</li>
</ul>
</section>
{% include 'github.html' %}
{% block content %}{% endblock %}
<footer>
<a id="feed" href="/feeds/all.atom.xml">
<img alt="RSS Logo" src="/theme/rss.svg" />
</a>
</footer>
</div>
</body>

View file

@ -14,7 +14,7 @@
<section class="section index">
{% if category != "lectures" %}
{% for year, year_articles in articles | groupby('date.year') | reverse %}
<h2>Articles de {{ year }}</h2>
<h2>En {{ year }}</h2>
<ul>
{% for article in year_articles %}
<li>

View file

@ -1,32 +1,34 @@
{% extends "base.html" %}
{% block content %}
<header>
<h1 class="post-title">{{ SITENAME }}</h1>
</header>
<article>
<p>
👋 <strong>Bienvenue par ici</strong>, je suis Alexis, un développeur intéressé par les
dynamiques collectives, les libertés numériques et la&nbsp;facilitation.
👋 <strong>Bienvenue par ici</strong>,
</p>
<p>
Vous retrouverez sur ce site <a href="/weeknotes">mes notes hebdomadaires</a>, quelques
<a href="/journal">billets de blog</a>, des <a href="/lectures">notes de lectures</a>, <a href="/code">des bouts
de code</a> et <a href="/ecriture">textes</a> que je veux garder quelque part. Bonne lecture&nbsp;!
Vous retrouverez sur ce bout de toile <a href="/weeknotes">mes notes hebdomadaires</a>, un
<a href="/journal">journal</a>, des <a href="/lectures">notes de lectures</a>, <a href="/code">des bouts
de code</a> et <a href="/ecriture">des textes</a> que je veux garder quelque part. Bonne lecture&nbsp;!
</p>
<p>
Pour me contacter, envoyez-moi un email sur <code>alexis@</code> ce domaine (en enlevant <code>blog.</code>).
</p>
</article>
{% if articles %}
</article>
{% if articles %}
<hr />
<h2>Les derniers articles</h2>
<ul>
<ul class="items">
{% for article in articles[0:20] %}
<li>
<time datetime="{{ article.date.isoformat() }}">{{ article.date.strftime("%Y-%m-%d") }}</time>
{% set category_description = CATEGORIES_DESCRIPTION.get(article.category)[0] %}
<li class="item">
{% set category_description = CATEGORIES_DESCRIPTION.get(article.category)[0] %}
<a href="{{ SITEURL }}/{{ article.url }}" id="page-title">{{ category_description }}: {{ article.title.replace(category_description, "") }}</a>
<time datetime="{{ article.date.isoformat() }}">{{ article.date.strftime("%Y-%m-%d") }}</time>
</li>
{% endfor %}
</ul>
<a href="archives.html">Voir tous les articles</a>
Voir <a href="archives.html">toutes les archives</a>
<hr />
{% endif %}
{% endblock content %}
{% endif %}
{% endblock content %}

View file

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
PATH = "content"
AUTHOR = ""
AUTHOR = "Alexis"
SITENAME = "Alexis Métaireau"
THEME = "mnmlist"
DISQUS_SITENAME = "notmyidea"
DEFAULT_PAGINATION = 3
STATIC_PATHS = ["images", "audio", "extra", "docs"]
STYLESHEET_URL = "/theme/css/main.css"
EXTRA_PATH_METADATA = {
"extra/keybase.txt": {"path": "keybase.txt"},
"extra/favicon.ico": {"path": "favicon.ico"},
@ -31,8 +29,8 @@ TAG_FEED_ATOM = "feeds/tags/{slug}.atom.xml"
DEFAULT_DATE_FORMAT = "%d %B %Y"
LINKS = []
PLUGIN_PATHS = ["."]
PLUGINS = ["simplereader", "isbn_downloader"]
PLUGIN_PATHS = ["plugins"]
PLUGINS = ["simplereader", "isbn_downloader", "neighbors"]
CACHE_OUTPUT_DIRECTORY = "cache"
CACHE_DOMAIN = "/cache/"
@ -43,12 +41,16 @@ CATEGORY_SAVE_AS = "{slug}/index.html"
CATEGORY_URL = "{slug}/"
MENU = [
("Journal", "/journal/index.html", "journal"),
("Code, etc.", "/code/", "code"),
("Notes hebdo", "/weeknotes/", "weeknotes"),
("Lectures", "/lectures/", "lectures"),
("Projets", "/projets.html", "projets"),
("Écriture", "/ecriture", "ecriture"),
# ("Journal", "/journal/index.html", "journal"),
# ("Code", "/code/", "code"),
# ("Notes hebdo", "/weeknotes/", "weeknotes"),
# ("Lectures", "/lectures/", "lectures"),
# ("Projets", "/projets.html", "projets"),
# ("Écriture", "/ecriture", "ecriture"),
]
FOOTERITEMS = [
("Pro", "/projets/"),
]
CATEGORIES_DESCRIPTION = {
@ -61,7 +63,7 @@ CATEGORIES_DESCRIPTION = {
"Quelques notes prises au détour d'une lecture, plutôt pour ne pas les oublier et me remémorer le livre quand j'en ai besoin.",
),
"code": (
"Code, etc.",
"Code",
"Des bouts de trucs liés au code, que je trouve utiles de stocker quelque part (en anglais)",
),
"journal": (

69
plugins/neighbors.py Normal file
View file

@ -0,0 +1,69 @@
"""
Neighbor Articles Plugin for Pelican
====================================
This plugin adds ``next_article`` (newer) and ``prev_article`` (older)
variables to the article's context
"""
from pelican import signals
def iter3(seq):
"""Generate one triplet per element in 'seq' following PEP-479."""
nxt, cur = None, None
for prv in seq:
if cur:
yield nxt, cur, prv
nxt, cur = cur, prv
# Don't yield anything if empty seq
if cur:
# Yield last element in seq (also if len(seq) == 1)
yield nxt, cur, None
def get_translation(article, prefered_language):
if not article:
return None
for translation in article.translations:
if translation.lang == prefered_language:
return translation
return article
def set_neighbors(articles, next_name, prev_name):
for nxt, cur, prv in iter3(articles):
setattr(cur, next_name, nxt)
setattr(cur, prev_name, prv)
for translation in cur.translations:
setattr(translation, next_name, get_translation(nxt, translation.lang))
setattr(translation, prev_name, get_translation(prv, translation.lang))
def neighbors(generator):
set_neighbors(generator.articles, "next_article", "prev_article")
for category, articles in generator.categories:
articles.sort(key=lambda x: x.date, reverse=True)
set_neighbors(articles, "next_article_in_category", "prev_article_in_category")
# support for the More Categories plugin
for category, articles in generator.categories:
articles.sort(key=lambda x: x.date, reverse=True)
index = category.name.count("/")
next_name = f"next_article_in_subcategory{index}"
prev_name = f"prev_article_in_subcategory{index}"
set_neighbors(articles, next_name, prev_name)
# support for the Subcategory Plugin
if hasattr(generator, "subcategories"):
for subcategory, articles in generator.subcategories:
articles.sort(key=lambda x: x.date, reverse=True)
index = subcategory.name.count("/")
next_name = f"next_article_in_subcategory{index}"
prev_name = f"prev_article_in_subcategory{index}"
set_neighbors(articles, next_name, prev_name)
def register():
signals.article_generator_finalized.connect(neighbors)

View file

@ -4,3 +4,4 @@ datefinder
typogrify
ghp-import
requests
pelican-neighbors