> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# TextBuilder

> Utilisation détaillée de la classe TextBuilder pour construire en PHP le texte enrichi (facets) de Bluesky.

## Qu'est-ce que TextBuilder ?

`TextBuilder` est une classe qui permet de composer par chaînage de méthodes les **facets** (annotations de texte enrichi) définis par l'AT Protocol de Bluesky.

Le corps d'une publication Bluesky est du texte brut, mais pour afficher des mentions, liens et hashtags, il faut envoyer un tableau `facets` indiquant la position (offset en octets) et le type dans le texte. `TextBuilder` effectue automatiquement ce calcul d'offset et la construction du tableau.

```mermaid theme={null}
sequenceDiagram
    participant App as Application Laravel
    participant TB as TextBuilder
    participant Post as Enregistrement Post
    participant AT as AT Protocol

    App->>TB: TextBuilder::make('Hello')
    App->>TB: ->mention('@alice.bsky.social')
    App->>TB: ->link('https://example.com')
    App->>TB: ->tag('#Laravel')
    TB->>TB: Calcul des offsets en octets<br>et construction du tableau facets
    TB->>Post: toPost()
    Post->>AT: Bluesky::post($post)
    AT-->>App: Réponse
```

## Création de texte de base

### TextBuilder::make()

Utilisez `TextBuilder::make()` pour créer une instance avec un texte initial. Le texte initial est optionnel.

```php theme={null}
use Revolution\Bluesky\RichText\TextBuilder;

$builder = TextBuilder::make(text: 'Hello Bluesky');
```

### text()

`text()` ajoute du texte à la fin.

```php theme={null}
$builder = TextBuilder::make()
    ->text('Hello ')
    ->text('Bluesky');

// $builder->text === 'Hello Bluesky'
```

### newLine()

Ajoute un saut de ligne. Vous pouvez spécifier le nombre de lignes avec `count` (par défaut : 1).

```php theme={null}
$builder = TextBuilder::make('Ligne 1')
    ->newLine()
    ->text('Ligne 2')
    ->newLine(count: 2)
    ->text('Ligne 4');
```

### toPost()

Convertit l'instance `TextBuilder` en enregistrement `Post`. Peut être transmis directement à `Bluesky::post()`.

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\RichText\TextBuilder;

$post = TextBuilder::make('Publication simple')->toPost();

$response = Bluesky::withToken()->post($post);
```

### Post::build()

Vous pouvez également utiliser `Post::build()` en lui passant une closure. La valeur de retour est un `Post`.

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\RichText\TextBuilder;

$post = Post::build(function (TextBuilder $builder) {
    $builder->text('Hello Bluesky');
});

$response = Bluesky::withToken()->post($post);
```

## Ajout de mentions (`@mention`)

`mention()` ajoute un facet de mention.

```php theme={null}
$builder->mention(text: '@alice.bsky.social');
```

### Résolution automatique du DID

Si vous omettez `did`, le DID sera automatiquement résolu depuis le handle (`Bluesky::resolveHandle()` est appelée).

```php theme={null}
// Résolution automatique du DID (un appel API se produit)
$builder->mention('@alice.bsky.social');
```

### Spécification explicite du DID

Si le DID est déjà connu, vous pouvez le passer explicitement pour éviter l'appel API.

```php theme={null}
// Spécifier directement le DID (recommandé : pas d'appel API)
$builder->mention(text: '@alice.bsky.social', did: 'did:plc:xxxxxxxxxxxxxxxxxxxx');
```

<Tip>
  Si vous utilisez beaucoup de mentions en production, mettre le DID en cache permet de réduire les appels API.
</Tip>

## Intégration de liens (URL)

`link()` ajoute un facet de lien.

```php theme={null}
// L'URL elle-même sert de texte affiché
$builder->link('https://laravel.com');

// Spécifier séparément le texte affiché et l'URL
$builder->link(text: 'Site officiel Laravel', uri: 'https://laravel.com');
```

Si `uri` est omis, `text` sera utilisé tel quel comme URI.

## Ajout de hashtags

`tag()` ajoute un facet de hashtag.

```php theme={null}
// Passer un texte commençant par # extrait automatiquement le tag
$builder->tag('#Laravel');

// Spécifier explicitement la chaîne du tag (sans #)
$builder->tag(text: '#Laravel', tag: 'Laravel');
```

## Construction de texte composite

Vous pouvez combiner plusieurs facets pour créer un texte de publication enrichi.

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\RichText\TextBuilder;

$post = TextBuilder::make('J\'ai publié un nouvel article !')
    ->newLine(count: 2)
    ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
    ->text(' j\'aimerais que vous le lisiez aussi.')
    ->newLine()
    ->link(text: 'Lire l\'article', uri: 'https://example.com/article/1')
    ->newLine()
    ->tag('#Laravel')
    ->text(' ')
    ->tag('#PHP')
    ->toPost();

$response = Bluesky::withToken()->post($post);
```

### Écriture avec Post::build()

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\RichText\TextBuilder;

$post = Post::build(function (TextBuilder $builder) {
    $builder->text('J\'ai publié un nouvel article !')
            ->newLine(count: 2)
            ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
            ->text(' j\'aimerais que vous le lisiez aussi.')
            ->newLine()
            ->link(text: 'Lire l\'article', uri: 'https://example.com/article/1')
            ->newLine()
            ->tag('#Laravel')
            ->text(' ')
            ->tag('#PHP');
});

$response = Bluesky::withToken()->post($post);
```

## Intégration avec les publications

### Combinaison avec Bluesky::post()

```php theme={null}
use Revolution\Bluesky\Facades\Bluesky;
use Revolution\Bluesky\RichText\TextBuilder;

$post = TextBuilder::make('test')
    ->newLine()
    ->link('https://bsky.app/')
    ->toPost();

$response = Bluesky::withToken()->post($post);
```

### Combinaison avec le canal Notification

Vous pouvez utiliser `Post::build()` dans la méthode `toBluesky()` de `BlueskyChannel`.

```php theme={null}
use Illuminate\Notifications\Notification;
use Revolution\Bluesky\Notifications\BlueskyChannel;
use Revolution\Bluesky\Record\Post;
use Revolution\Bluesky\RichText\TextBuilder;

class DeployedNotification extends Notification
{
    public function __construct(
        private string $url,
    ) {}

    public function via(object $notifiable): array
    {
        return [BlueskyChannel::class];
    }

    public function toBluesky(object $notifiable): Post
    {
        return Post::build(function (TextBuilder $builder) {
            $builder->text('Le déploiement est terminé')
                    ->newLine()
                    ->link(text: 'Vérifier', uri: $this->url)
                    ->newLine()
                    ->tag('#Laravel');
        });
    }
}
```

## Détection automatique des facets

Vous pouvez également détecter automatiquement les `@mention`, URL et `#hashtag` du texte pour configurer les facets.

```php theme={null}
use Revolution\Bluesky\RichText\TextBuilder;

$builder = TextBuilder::make('@alice.bsky.social test https://example.com #alice')
    ->detectFacets();

// Il est possible d'ajouter d'autres facets après detectFacets()
$builder->newLine()->tag('#bob');

$post = $builder->toPost();
```

<Warning>
  `detectFacets()` fonctionne par expressions régulières. Si vous voulez garantir la liaison, il est plus sûr d'utiliser explicitement `link()`, `mention()` et `tag()`.
</Warning>

## Ajout de facets personnalisés

La méthode `facet()` permet d'ajouter directement n'importe quel tableau de facet.

```php theme={null}
use Revolution\Bluesky\RichText\TextBuilder;

$builder = TextBuilder::make();

$builder->facet([
    'index' => [
        'byteStart' => 0,
        'byteEnd' => 5,
    ],
    'features' => [
        [
            '$type' => 'app.bsky.richtext.facet#link',
            'uri' => 'https://example.com',
        ],
    ],
]);
```

## Limites de caractères et considérations

### Offset en octets et grapheme

Les index de facet d'AT Protocol sont spécifiés en **offsets d'octets UTF-8**. `TextBuilder` utilise en interne `strlen()` pour calculer le nombre d'octets.

Les caractères multi-octets comme le japonais ou les emojis consomment plusieurs octets par caractère, donc l'offset est déterminé **en nombre d'octets et non en nombre de caractères**.

```php theme={null}
// 'Hello' fait 5 octets en UTF-8 (1 caractère = 1 octet)
// '日本語' fait 9 octets en UTF-8 (1 caractère = 3 octets)
$builder = TextBuilder::make('日本語');
// strlen($builder->text) === 9
```

### Limite de caractères des publications

Les publications Bluesky sont limitées à **300 caractères en grapheme (caractères affichés)** maximum. La limite étant en grapheme et non en octets, vous pouvez écrire 300 caractères même en japonais.

```php theme={null}
use Illuminate\Support\Str;

$text = 'Voici le texte de la publication.';

// Vérifier le nombre de caractères en grapheme
$length = Str::length($text); // équivalent à mb_strlen()
```

<Info>
  `TextBuilder` lui-même ne vérifie pas la longueur. Si vous envoyez une publication dépassant 300 graphemes, l'API AT Protocol retournera une erreur.
</Info>

## Liste des méthodes

| Méthode                                      | Description                                             |
| -------------------------------------------- | ------------------------------------------------------- |
| `TextBuilder::make(string $text = '')`       | Créer une instance                                      |
| `text(string $text)`                         | Ajouter du texte à la fin                               |
| `newLine(int $count = 1)`                    | Ajouter un saut de ligne                                |
| `mention(string $text, ?string $did = null)` | Ajouter un facet de mention                             |
| `link(string $text, ?string $uri = null)`    | Ajouter un facet de lien                                |
| `tag(string $text, ?string $tag = null)`     | Ajouter un facet de hashtag                             |
| `detectFacets()`                             | Détecter automatiquement les facets à partir du texte   |
| `facet(array $facet)`                        | Ajouter un facet personnalisé                           |
| `resetFacets()`                              | Réinitialiser tous les facets                           |
| `toPost()`                                   | Convertir en enregistrement `Post`                      |
| `toArray()`                                  | Convertir en tableau `['text' => ..., 'facets' => ...]` |

<Info>
  Source: [src/RichText/TextBuilder.php](https://github.com/invokable/laravel-bluesky/blob/main/src/RichText/TextBuilder.php)
</Info>


## Related topics

- [Client de base - Laravel Bluesky](/fr/packages/laravel-bluesky/basic-client.md)
- [Laravel Bluesky](/fr/packages/laravel-bluesky/index.md)
- [Canal de notification - Laravel Bluesky](/fr/packages/laravel-bluesky/notification.md)
- [Query Builder](/fr/query-builder.md)
- [Recherche](/fr/search.md)
