> ## 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

> Uso detallado de la clase TextBuilder para construir rich text (facets) de Bluesky con PHP.

## Qué es TextBuilder

`TextBuilder` es la clase para construir mediante encadenamiento de métodos los **facets** (anotaciones de rich text) que define el AT Protocol de Bluesky.

El cuerpo de las publicaciones de Bluesky es texto plano, pero para mostrar menciones, enlaces y hashtags hay que enviar además un array `facets` que indica la posición (byte offset) y el tipo dentro del texto. `TextBuilder` calcula automáticamente esos offsets y construye el array.

```mermaid theme={null}
sequenceDiagram
    participant App as Aplicación Laravel
    participant TB as TextBuilder
    participant Post as Record 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: Calcula byte offsets y<br>construye el array facets
    TB->>Post: toPost()
    Post->>AT: Bluesky::post($post)
    AT-->>App: Respuesta
```

## Creación básica de texto

### TextBuilder::make()

Crea una instancia indicando el texto inicial con `TextBuilder::make()`. El texto inicial se puede omitir.

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

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

### text()

Con `text()` se añade texto al final.

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

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

### newLine()

Añade un salto de línea. Con `count` puedes indicar el número de líneas (por defecto: 1).

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

### toPost()

Convierte la instancia de `TextBuilder` en un record `Post`. Se puede pasar directamente a `Bluesky::post()`.

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

$post = TextBuilder::make('Publicación simple')->toPost();

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

### Post::build()

También puedes usar `Post::build()` pasándole una clausura. Devuelve 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);
```

## Añadir menciones (`@mention`)

Con `mention()` se añade un facet de mención.

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

### Resolución automática del DID

Si omites `did`, se resuelve automáticamente a partir del handle (se llama a `Bluesky::resolveHandle()`).

```php theme={null}
// Resuelve el DID automáticamente (se produce una llamada a la API)
$builder->mention('@alice.bsky.social');
```

### Indicar el DID explícitamente

Si ya conoces el DID, puedes pasarlo explícitamente para evitar la llamada a la API.

```php theme={null}
// Indicar el DID directamente (recomendado: sin llamada a la API)
$builder->mention(text: '@alice.bsky.social', did: 'did:plc:xxxxxxxxxxxxxxxxxxxx');
```

<Tip>
  Si vas a usar muchas menciones en producción, cachea los DIDs para reducir las llamadas a la API.
</Tip>

## Incluir enlaces (URL)

Con `link()` se añade un facet de enlace.

```php theme={null}
// El propio URL se muestra como texto
$builder->link('https://laravel.com');

// Especifica por separado texto y URL
$builder->link(text: 'Web oficial de Laravel', uri: 'https://laravel.com');
```

Si omites `uri`, se usa `text` como URI tal cual.

## Añadir hashtags

Con `tag()` se añade un facet de hashtag.

```php theme={null}
// Al pasar un texto que empieza por #, la etiqueta se extrae automáticamente
$builder->tag('#Laravel');

// Indica explícitamente la cadena de la etiqueta (cadena sin #)
$builder->tag(text: '#Laravel', tag: 'Laravel');
```

## Construir texto compuesto

Puedes combinar varios facets para crear textos de publicación enriquecidos.

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

$post = TextBuilder::make('¡Nuevo artículo publicado!')
    ->newLine(count: 2)
    ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
    ->text(' también quiero que lo leas.')
    ->newLine()
    ->link(text: 'Leer el artículo', uri: 'https://example.com/article/1')
    ->newLine()
    ->tag('#Laravel')
    ->text(' ')
    ->tag('#PHP')
    ->toPost();

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

### Escritura con 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('¡Nuevo artículo publicado!')
            ->newLine(count: 2)
            ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
            ->text(' también quiero que lo leas.')
            ->newLine()
            ->link(text: 'Leer el artículo', uri: 'https://example.com/article/1')
            ->newLine()
            ->tag('#Laravel')
            ->text(' ')
            ->tag('#PHP');
});

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

## Integración con publicaciones

### Combinado con 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);
```

### Combinado con el canal de Notification

En el método `toBluesky()` de `BlueskyChannel` puedes usar `Post::build()`.

```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('El despliegue ha finalizado')
                    ->newLine()
                    ->link(text: 'Ver', uri: $this->url)
                    ->newLine()
                    ->tag('#Laravel');
        });
    }
}
```

## Detección automática de facets

También puedes detectar automáticamente `@mention`, URLs y `#hashtag` dentro del texto y añadir los facets.

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

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

// Después de detectFacets() aún puedes añadir más facets
$builder->newLine()->tag('#bob');

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

<Warning>
  `detectFacets()` se basa en expresiones regulares. Si quieres asegurar el enlace, es más fiable usar explícitamente `link()`, `mention()` o `tag()`.
</Warning>

## Añadir facets personalizados

Con el método `facet()` puedes añadir directamente un array de facet arbitrario.

```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',
        ],
    ],
]);
```

## Límite de caracteres y consideraciones

### Byte offsets y graphemes

El índice de facet del AT Protocol se especifica en **byte offsets UTF-8**. Internamente `TextBuilder` usa `strlen()` para calcular el número de bytes.

Los caracteres multibyte como el japonés o los emojis consumen varios bytes por carácter, así que el offset se determina **por bytes, no por caracteres**.

```php theme={null}
// 'Hello' ocupa 5 bytes en UTF-8 (1 carácter = 1 byte)
// '日本語' ocupa 9 bytes en UTF-8 (1 carácter = 3 bytes)
$builder = TextBuilder::make('日本語');
// strlen($builder->text) === 9
```

### Límite de caracteres por publicación

Las publicaciones de Bluesky tienen un **máximo de 300 caracteres en graphemes** (caracteres visibles). El límite se aplica en graphemes, no en bytes, así que puedes escribir hasta 300 caracteres incluso en japonés.

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

$text = 'Este es el texto de la publicación.';

// Comprueba la longitud en graphemes
$length = Str::length($text); // equivalente a mb_strlen()
```

<Info>
  El propio `TextBuilder` no verifica el número de caracteres. Si envías una publicación con más de 300 graphemes, la API del AT Protocol devolverá un error.
</Info>

## Lista de métodos

| Método                                       | Descripción                                          |
| -------------------------------------------- | ---------------------------------------------------- |
| `TextBuilder::make(string $text = '')`       | Crea una instancia                                   |
| `text(string $text)`                         | Añade texto al final                                 |
| `newLine(int $count = 1)`                    | Añade saltos de línea                                |
| `mention(string $text, ?string $did = null)` | Añade un facet de mención                            |
| `link(string $text, ?string $uri = null)`    | Añade un facet de enlace                             |
| `tag(string $text, ?string $tag = null)`     | Añade un facet de hashtag                            |
| `detectFacets()`                             | Detecta facets automáticamente en el texto           |
| `facet(array $facet)`                        | Añade un facet personalizado                         |
| `resetFacets()`                              | Resetea todos los facets                             |
| `toPost()`                                   | Convierte a record `Post`                            |
| `toArray()`                                  | Convierte a array `['text' => ..., 'facets' => ...]` |

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


## Related topics

- [Basic client - Laravel Bluesky](/es/packages/laravel-bluesky/basic-client.md)
- [Laravel Bluesky](/es/packages/laravel-bluesky/index.md)
- [Canal de notificaciones - Laravel Bluesky](/es/packages/laravel-bluesky/notification.md)
- [Query builder](/es/query-builder.md)
- [Búsqueda](/es/search.md)
