[🇬🇧 Read in English](README.md)

# antidote-api-js

`antidote-api-js` est une bibliothèque JavaScript/TypeScript permettant d'intégrer le correcteur Antidote dans vos applications Web ou hybrides (Cordova, Electron, etc.). Elle fournit des fonctions pour lancer le correcteur, gérer la communication avec Antidote Web et interagir avec les textes à corriger.

## Installation

```bash
npm install @druide-informatique/antidote-api-js
```

ou

```bash
yarn add @druide-informatique/antidote-api-js
```

## Construction

### Paquet

```bash
npm install
npm run build
```

## API de communication avec le Connecteur Antidote ou Antidote Web directement

Objet principal : Antidote

```typescript
import { ConnecteurAntidote } from '@druide-informatique/antidote-api-js/fr';
import { Antidote } from '@druide-informatique/antidote-api-js/fr';
```

Fonctions de lancement du correcteur
* `Antidote.lanceCorrecteurAntidoteWeb(noeud, creeFenetre?, fonctionCorrectionAppliquee?)`
* `Antidote.lanceCorrecteurAntidote(noeud, creeFenetre?, fonctionCorrectionAppliquee?)`

Paramètres
* `noeud`: `HTMLElement | HTMLElement[]` – Le ou les éléments DOM à corriger.
* `creeFenetre?`: `() => FenetreAntidoteWeb | null` – Fonction optionnelle pour créer la fenêtre du correcteur.
* `fonctionCorrectionAppliquee?`: `() => void` – Fonction optionnelle appelée après l’application des corrections.

Détection du connecteur
* `ConnecteurAntidote.estDetectay()`: boolean – Vérifie si le Connecteur Antidote est disponible.

⸻

Création de fenêtres Antidote
* `creeFenetreCordova()`: `FenetreAntidoteWeb` – Pour les applications Cordova.
* `creeFenetreWindowPostMessage()`: `FenetreAntidoteWeb | null` – Pour la communication via _postMessage_.

### Interface FenetreAntidoteWeb

```typescript
interface FenetreAntidoteWeb {
  envoieMessageVersAWeb(leMessageJSON: MessageAntidote): void;
  initialiseCommunicationAntidoteWeb(): void;
  affiche(): void;
  retourneAuTexteur(): void;
}
```

### Gestion des messages depuis Antidote Web

```typescript
gestionnaireMessageDepuisAWeb(event: MessageEvent<MessageAntidote>): void
```

### Types

```typescript
type NoeudOuListeNoeuds = HTMLElement | HTMLElement[];

interface MessageAntidote {
}

type FonctionCorrectionAppliquee = () => void;
type FonctionCreeFenetreAntidoteWeb = () => FenetreAntidoteWeb | null;
```

### Exemple d’utilisation

```typescript
import { Antidote, creeFenetreWindowPostMessage } from '@druide-informatique/antidote-api-js/fr';

const textarea = document.querySelector('#monTextarea');

Antidote.lanceCorrecteurAntidoteWeb(
    textarea,
    creeFenetreWindowPostMessage,
    () => console.log('Correction appliquée !')
);
```

## API WebSocket — Connexion directe avec Connectix

Cette API permet la communication avec les outils d’[Antidote](https://antidote.info) : Correcteur, Dictionnaires et Guides.

Une fois le protocole établi, l’application permet une communication bidirectionnelle entre le texte de l’application et Antidote. Les corrections proposées par Antidote sont envoyées par message pour n’effectuer que les remplacements minimums au texte original. Il est possible d’ouvrir le correcteur pour corriger un ou plusieurs documents — volumineux ou non — et d’ouvrir les dictionnaires ou les guides sur une courte phrase ou un seul mot. Pour les dictionnaires, cette API permet de gérer la fonction _[remplacer](https://www.antidote.info/fr/documentation/guide-utilisation/les-dictionnaires/fonctionnalites/remplacer-un-mot-dans-le-texte)_.

### Exemple

#### Sous-classe d’AgentTexteur

L’AgentTexteur représente votre application auprès d’Antidote. C’est lui qui fournira les informations de votre application à Antidote. Il faut au minimum définir les fonctions `configuration`, `corrigeDansTexteur`, `peutCorriger` et `zonesACorriger`. Dans l’exemple suivant, on veut corriger le contenu des champs dans `champsACorriger`.

```typescript
import { appliqueCorrection, recupereTexte, selectionneIntervalle } from '@druide-informatique/antidote-api-js/fr';

export class AgentTexteurTextArea extends AgentTexteur {
  private champsACorriger: HTMLElement[];

  constructor(champsACorriger: HTMLElement[]) {
    super();
    this.champsACorriger = champsACorriger;
  }

  corrigeDansTexteur(params: ParamsRemplace): boolean {
    const champACorriger = this.champsACorriger[Number(params.idZone)];
    const plageACorriger = { debut: params.positionRemplacementDebut,
      fin: params.positionRemplacementFin,
      chaine: params.nouvelleChaine };
    return appliqueCorrection(champACorriger, plageACorriger);
  }

  configuration(): ConfigurationTexteur {
    return {};
  }

  peutCorriger(params: ParamsEditionPossible): boolean {
    const champACorriger = this.champsACorriger[Number(params.idZone)];
    const texte = recupereTexte(champACorriger);
    return texte.substring(params.positionDebut, params.positionFin) == params.contexte;
  }

  selectionneIntervalle(params: ParamsSelectionne): void {
    const champACorriger = this.champsACorriger[Number(params.idZone)];
    selectionneIntervalle(champACorriger, params.positionDebut, params.positionFin);
  }

  zonesACorriger(_params: ParamsDonneZonesACorriger): ZoneDeTexte[] {
    return this.champsACorriger.map(function(value, index) {
      return {
        texte: recupereTexte(value),
        idZone: index.toString(),
        zoneEstEnFocus: index == 0
      };
    });
  }
}
```

#### Utilisation d’AgentConnectix

Pour communiquer avec Antidote, il faut utiliser la classe AgentConnectix. Pour l’instancier, on doit fournir en paramètre la sous-classe d’AgentTexteur. Une fois la classe instanciée, on peut ouvrir l’un des trois outils : Correcteur, Dictionnaires ou Guides.

```typescript
const zones_a_corriger = [
  document.getElementById('text_area') as HTMLElement,
  document.getElementById('div_editable') as HTMLElement
];
const agentTexteur = new AgentTexteurTextArea(zones_a_corriger);
const agent = new AgentConnectix(agentTexteur, function() { return 3000; });
await agent.connecteAvecAntidote(); // Initialisation de la communication avec le processus AgentConnectix.
agent.lanceCorrecteur(); // Lance le correcteur.
```

#### Exemple complet

Pour un exemple complet, voir le projet [application-electron](https://github.com/DruideInformatiqueInc/application-electron).

### Principe

L’AgentConnectix est un processus démon (_deamon_) qui permet de gérer la communication entre vos texteurs et Antidote. C’est lui qui permet de corriger vos textes directement dans les applications. L’AgentConnectix a un serveur WebSocket local (`localhost`) qui est ouvert et avec lequel on peut communiquer afin de lancer Antidote (Correcteur, Dictionnaires et Guides).

#### Connexion au serveur WebSocket d’AgentConnectix

Pour trouver le port du serveur WebSocket, on appelle l’application en ligne de commande `AgentConnectixConsole` avec le paramètre `--api`. L’emplacement est différent pour chaque plateforme :

* **macOS** :
  * `/Applications/Antidote/Connectix 11.app/Contents/SharedSupport/AgentConnectixConsole`
  * `/Applications/Antidote/Connectix 12.app/Contents/SharedSupport/AgentConnectixConsole`
* **Windows** : Le chemin du dossier où se trouve `AgentConnectixConsole` est indiqué dans la clé de registre suivante : `HKEY_LOCAL_MACHINE\SOFTWARE\Druide informatique inc.\Connectix`, dans la valeur `DossierConnectix`.

L’application retourne la valeur du port en format JSON.

```bash
% AgentConnectixConsole --api
{"port": 59004}
```

Avec cette valeur, on se connecte au serveur WebSocket avec l’URL `ws://localhost:PORT`, où `PORT` doit être remplacé par la valeur obtenue précédemment.

## Protocole de communication

Tous les paquets reçus et envoyés sont des documents JSON. Afin de pouvoir recevoir ou envoyer des documents JSON de toutes les tailles, un système de paquets est mis en place. Chaque paquet est lui-même un document JSON de format :

```json
{
  "idPaquet": x,
  "totalPaquet": y,
  "donnees": z
}
```

* `idPaquet` correspond au numéro du paquet envoyé (entre 0 et `totalPaquet - 1`);
* `totalPaquet` correspond au nombre total de paquets dans le message envoyé;
* `donnees` correspond à une chaine qui, une fois tous les paquets reçus, pourra être concaténée pour former un document JSON.

Avec ce protocole, on ne peut pas mélanger les paquets de messages différents. Les paquets d’un même message doivent être envoyés successivement.

Une fois que les paquets `0` à `N-1` sont reçus, on concatène les chaines de la valeur `donnees`. Cette chaine reconstruite est alors un document JSON que l’on peut traiter comme un message.

### Messages

**Amorcer un appel vers Antidote**

Pour lancer un appel vers Antidote, il faut envoyer un des messages suivant :

* `LanceOutils` avec les paramètres `outils_api` et `version_api` avec le document JSON suivant :

```jsonc
{
  "version_api": 2,
  "message": "LanceOutils",
  "outil_api": "Correcteur" | "Dictionnaires" | "Guides"
}
```


* `AntiOups` avec les paramètres suivant :

```jsonc
{
  "version_api": 2,
  "message": "AntiOups",
  "sujet": sujet, // chaine
  "corps": corps, // chaine
  "destinataires": [ noms des destinataires ], // facultatif, liste de chaine des destinataires des courriels
  "id": identifiant // pour permettre d’associer les réponses à l’appel
}
```

**Messages entrants à répondre**

Antidote répond avec un document JSON du format suivant :

```jsonc
{
  "idMessage": identifiant,
  "message": message,
  "donnees": booleen | chaine | dictionnaire
}
```

où

* `idMessage` est une chaine aléatoire qui doit être retournée lors de la réponse au message.
* `message` est un des types définis ci-dessous.
* `donnees` sont les paramètres du message. Ces paramètres sont facultatifs selon le type de message et peuvent être un booléen, une chaine ou un dictionnaire.

Voici la liste des différents messages possibles avec la réponse qui doit être retournée. Les paramètres sont dans la valeur de la clé `donnees` du message.

* `init` : Message d’initialisation de la correction.

  * **Paramètres** : _Pas de paramètre_.
  * **Réponse** : Configuration du texteur. Toutes les clés sont optionnelles.

    ```jsonc
    {
      // Chaine de la clé `idMessage` reçue dans le message `init`.
      "idMessage": "",
      // Titre du document. Sera affiché dans la barre-titre du correcteur d’Antidote.
      "titreDocument": "",
      // Le type de retour de charriot (ex. : "\n", "\r", "\r\n").
      "retourCharriot" : "",
      // Type indiquant comment gérer la cache de l’état de correction (seule valeur possible :
      // `forceEmplacementFichier`).
      "typeIdentifiantCache": "", 
      // Indique si on permet l’insertion de retour de charriot à partir du correcteur.
      "permetRetourCharriot": true | false,
      // Indique si le texteur permet l’usage de l’espace insécable.
      "permetEspaceInsecable": true | false,
      // Indique si le texteur permet l’usage de l’espace fine.
      "permetEspaceFine": true | false,
      // Indique si le contexte permet l'envoi d'un courriel à partir d’Antidote (Anti-Oups!)
      "permet_envoi_courriel": true | false,
      // Indique s’il est possible de remplacer le texte sans nécessairement faire une sélection.
      "remplaceSansSelection": true | false,
      // Chaine en format base64 représentant l’état de la dernière correction. C’est une chaine
      // qui est générée par Antidote; elle ne doit pas être construite manuellement.
      "memoireCorrection": "",
      // Indique le type de document qui sera corrigé.
      "filtreActif": "texte" | "latex" | "markdown" | "subrip" | "html",
      // Dictionnaire JSON correspondant aux réglages à utiliser lors de la correction. Si rien
      // n’est spécifié, les réglages de l’utilisateur seront utilisés.
      "reglagesAntidote": {} 
    }
    ```

* `cheminDocument` : Message qui demande le chemin du document à corriger.
  * **Paramètres** : _Pas de paramètre_.
  * **Réponse** : Retourne le chemin du document dans la clé `donnees`.

    ```jsonc
    {
      // Chaine de la clé `idMessage` reçue dans le message `cheminDocument`.
      "idMessage": "",
      // Chemin du document en correction. Utilisé pour l’état de correction géré par Antidote.
      "donnees": "",
    }
    ```

* `docEstDisponible` : Message qui demande si le document en correction est encore accessible.
  * **Paramètres** : _Pas de paramètre_.
  * **Réponse** : Booléen indiquant s’il est encore possible de corriger le document. Il peut arriver que le document ne soit plus disponible lorsque l’utilisateur ferme le document en correction.

    ```jsonc
    {
      // Chaine de la clé `idMessage` reçue dans le message `docEstDisponible`.
      "idMessage": "",
      // Booléen qui indique l’état du document en correction.
      "donnees": true | false,
    }
    ```

* `donneZonesTexte` : Message qui demande les zones de texte à faire corriger.
  * **Paramètres** : 
  
    ```jsonc
    {
      // Si vrai, demande de retourner une seule zone de texte où se trouve la sélection.
      // Souvent vrai pour un appel aux dictionnaires et aux guides et faux pour le
      // correcteur.
      "pourSelectionActive": true | false,
    }
    ```

  * **Réponse** : La liste des zones de texte à faire corriger.

    ```jsonc
    {
      // Chaine de la clé `idMessage` reçue dans le message `donneZonesTexte`.
      "idMessage": "",
      // Liste des zones de texte.
      "donnees": [
        {
          // Chaine correspondant au texte à corriger.
          "texte": "",
          // Entier optionnel de la position de début de la sélection dans le texteur.
          // Ne pas indiquer s’il n’y a aucune sélection.
          "positionDebutSelection": x,
          // Entier optionnel de la position de fin de la sélection dans le texteur.
          // Ne pas indiquer s’il n’y a aucune sélection.
          "positionFinSelection": y,
          // Indique si cette zone a le focus (c.-à-d. la zone qui reçoit les événements
          // de clavier).
          "zoneEstEnFocus": true | false,
          // Identifiant de la zone de texte. Doit être unique et permettre de retrouver
          // facilement la zone selon cette chaine.
          "idZone": "",
          // Liste du style appliqué sur le texte (optionnel)
          "infoStyle": [
            {
              "positionDebut": x,
              "positionFin": y,
              "style": "gras" | "italique" | "exposant" | "indice" | "barre"
            }
          ]
        },
        ...
      ]
    }
    ```

* `editionPossible` : Message qui demande si le texte aux positions demandées est bien celui auquel Antidote s’attend.
  * **Paramètres** : 
  
    ```jsonc
    {
      "idZone": "", // Identifiant de la zone à vérifier.
      "contexte": "", // Chaine attendue aux positions `positionDebut` et `positionFin` dans la zone `idZone`.
      "positionDebut": x, // Position de début de la chaine à vérifier.
      "positionFin": y //  Position de fin de la chaine à vérifier.
    }
    ```

  * **Réponse** : Un booléen qui indique si la chaine est bien celle attendue.

    ```jsonc
    {
      // Chaine de la clé `idMessage` reçue dans le message `editionPossible`.
      "idMessage": "",
      // Booléen qui indique si la chaine est bien celle attendue.
      "donnees": true | false
    }
    ```

* `remplace` : Message qui demande d’effectuer un remplacement dans le document.
  * **Paramètres** : 
  
    ```jsonc
    {
      // Identifiant de la zone à corriger.
      "idZone": "",
      // Chaine à remplacer aux positions `positionRemplacementDebut`
      // et `positionRemplacementFin` dans la zone `idZone`.
      "nouvelleChaine": "", 
      // Position de début de la chaine à remplacer.
      "positionRemplacementDebut": x,
       // Position de fin de la chaine à remplacer.
      "positionRemplacementFin": y
    }
    ```

  * **Réponse** : Un booléen qui indique si le remplacement a bien pu se faire.

    ```jsonc
    {
      // Chaine de la clé `idMessage` reçue dans le message `remplace`.
      "idMessage": "",
      // Booléen qui indique si le remplacement a bien pu se faire.
      "donnees": true | false
    }
    ```

* `selectionne` : Message qui demande d’effectuer une sélection dans le document en correction.
  
  * **Paramètres** : 
  
    ```jsonc
    {
      // Identifiant de la zone à corriger.
      "idZone": "",
      // Position où commencer la sélection.
      "positionDebut": x,
      // Position où terminer la sélection.
      "positionFin": y
    }
    ```

  * **Réponse** : _Pas de réponse à envoyer_.

* `retourneAuDocument` : Message qui demande à mettre le texteur en avant-plan.
  
  * **Paramètres** : _Pas de paramètre_.

  * **Réponse** : _Pas de réponse à envoyer_.


* `nouvelleMemoireCorrection` : Message qui indique qu’un nouvel état de correction est disponible.

  * **Paramètres** : Chaine en format base64 contenant les données de l’état de correction à sauvegarder.

  * **Réponse** : _Pas de réponse à envoyer_.

* `reponseAntiOups` : Message qui donne le résultat de l'appel à l'Anti-Oups!. Selon le résulat, il faut demander l'ouverture du correcteur (message `LanceOutils` avec `Correcteur`).

  * **Paramètres** : 
  
    ```jsonc
    {
      // Identifiant de l'appel demandant une requête Anti-Oups!.
      "id": identifiant,
      // Optionel : code d'erreur de l'appel .
      "codeErreur": x, 
      // Optionel : liste des détections trouvées par l'Anti-Oups!
      // Une de ces détections devraient demander l’ouverture du correcteur
      "detections": [ ("erreurs" | "typographie" | "piecesJointes" | "langueInactive" | "tonVexant") ]
    }
    ```

  * **Réponse** : _Pas de réponse à envoyer_.

* `envoi` : Message qui demande à envoyer le courriel dont la correction est en cours.
  
  * **Paramètres** : _Pas de paramètre_.

  * **Réponse** : _Pas de réponse à envoyer_.
