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

> 在 PHP 中建立 Bluesky 富文字（facets）的 TextBuilder 類別詳細用法。

## 什麼是 TextBuilder

`TextBuilder` 是以方法鏈組合 Bluesky AT Protocol 所定義的 **facets**（富文字註記）的類別。

Bluesky 貼文本文為純文字，但若要顯示 mention、連結、hashtag，需要同時傳送標示文字中位置（位元組偏移）與種類的 `facets` 陣列。`TextBuilder` 會自動處理這些偏移計算與陣列建構。

```mermaid theme={null}
sequenceDiagram
    participant App as Laravel 應用程式
    participant TB as TextBuilder
    participant Post as 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: 計算位元組偏移並<br>建立 facets 陣列
    TB->>Post: toPost()
    Post->>AT: Bluesky::post($post)
    AT-->>App: 回應
```

## 基本文字建立

### TextBuilder::make()

以 `TextBuilder::make()` 指定初始文字並產生實體。初始文字可省略。

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

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

### text()

以 `text()` 於末尾追加文字。

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

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

### newLine()

加入換行。可透過 `count` 指定行數（預設：1）。

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

### toPost()

將 `TextBuilder` 實體轉換為 `Post` 記錄，可直接傳入 `Bluesky::post()`。

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

$post = TextBuilder::make('簡單的貼文')->toPost();

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

### Post::build()

也可透過將閉包傳入 `Post::build()` 的方式使用。回傳值為 `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);
```

## 加入 mention（`@mention`）

以 `mention()` 加入 mention facet。

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

### DID 自動解析

若省略 `did`，會從 handle 自動解析 DID（會呼叫 `Bluesky::resolveHandle()`）。

```php theme={null}
// 自動解析 DID（會發生 API 呼叫）
$builder->mention('@alice.bsky.social');
```

### 明確指定 DID

若已知 DID，明確傳入即可避免 API 呼叫。

```php theme={null}
// 直接指定 DID（建議：無需 API 呼叫）
$builder->mention(text: '@alice.bsky.social', did: 'did:plc:xxxxxxxxxxxxxxxxxxxx');
```

<Tip>
  若正式環境中頻繁使用 mention，將 DID 快取起來可減少 API 呼叫。
</Tip>

## 嵌入連結（URL）

以 `link()` 加入連結 facet。

```php theme={null}
// 直接以 URL 作為顯示文字
$builder->link('https://laravel.com');

// 分別指定顯示文字與 URL
$builder->link(text: 'Laravel 官方網站', uri: 'https://laravel.com');
```

若省略 `uri`，則會將 `text` 直接作為 URI。

## 加入 hashtag

以 `tag()` 加入 hashtag facet。

```php theme={null}
// 傳入以 # 開頭的文字會自動擷取 tag
$builder->tag('#Laravel');

// 明確指定 tag 字串（不含 # 的字串）
$builder->tag(text: '#Laravel', tag: 'Laravel');
```

## 組合文字的建構

可組合多個 facet 建立豐富的貼文文字。

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

$post = TextBuilder::make('已發佈新文章！')
    ->newLine(count: 2)
    ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
    ->text(' 也非常推薦一讀。')
    ->newLine()
    ->link(text: '閱讀文章', uri: 'https://example.com/article/1')
    ->newLine()
    ->tag('#Laravel')
    ->text(' ')
    ->tag('#PHP')
    ->toPost();

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

### 以 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('已發佈新文章！')
            ->newLine(count: 2)
            ->mention('@alice.bsky.social', did: 'did:plc:xxxx')
            ->text(' 也非常推薦一讀。')
            ->newLine()
            ->link(text: '閱讀文章', uri: 'https://example.com/article/1')
            ->newLine()
            ->tag('#Laravel')
            ->text(' ')
            ->tag('#PHP');
});

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

## 與貼文的整合

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

### 與 Notification 頻道結合

可在 `BlueskyChannel` 的 `toBluesky()` 方法中使用 `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('部署已完成')
                    ->newLine()
                    ->link(text: '前往確認', uri: $this->url)
                    ->newLine()
                    ->tag('#Laravel');
        });
    }
}
```

## facets 的自動偵測

也可自動偵測文字中的 `@mention`、URL、`#hashtag` 並設定 facets。

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

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

// detectFacets() 之後仍可繼續加入 facets
$builder->newLine()->tag('#bob');

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

<Warning>
  `detectFacets()` 是基於正規表達式的偵測。若要確保連結正確，明確使用 `link()`、`mention()`、`tag()` 會更可靠。
</Warning>

## 加入自訂 facet

透過 `facet()` 方法可直接加入任意的 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',
        ],
    ],
]);
```

## 字數限制與注意事項

### 位元組偏移與 grapheme

AT Protocol 的 facet 索引以 **UTF-8 位元組偏移** 指定。`TextBuilder` 內部使用 `strlen()` 計算位元組數。

日文、emoji 等多位元組字元即使 1 個字元也會佔用多個位元組，因此偏移由**位元組數而非字元數**決定。

```php theme={null}
// 'Hello' 以 UTF-8 為 5 位元組（1 字元 = 1 位元組）
// '日本語' 以 UTF-8 為 9 位元組（1 字元 = 3 位元組）
$builder = TextBuilder::make('日本語');
// strlen($builder->text) === 9
```

### 貼文字數限制

Bluesky 的貼文限制為 **grapheme（顯示上的字數）最多 300 字元**。限制以 grapheme 而非位元組數計，因此中文也可撰寫 300 字。

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

$text = '這是貼文的內容。';

// 以 grapheme 計算字數
$length = Str::length($text); // 等同於 mb_strlen()
```

<Info>
  `TextBuilder` 本身不執行字數檢查。若送出超過 300 grapheme 的貼文，AT Protocol API 會回傳錯誤。
</Info>

## 方法列表

| 方法                                           | 說明                                       |
| -------------------------------------------- | ---------------------------------------- |
| `TextBuilder::make(string $text = '')`       | 產生實體                                     |
| `text(string $text)`                         | 於末尾追加文字                                  |
| `newLine(int $count = 1)`                    | 加入換行                                     |
| `mention(string $text, ?string $did = null)` | 加入 mention facet                         |
| `link(string $text, ?string $uri = null)`    | 加入連結 facet                               |
| `tag(string $text, ?string $tag = null)`     | 加入 hashtag facet                         |
| `detectFacets()`                             | 自動偵測文字中的 facets                          |
| `facet(array $facet)`                        | 加入自訂 facet                               |
| `resetFacets()`                              | 重設所有 facets                              |
| `toPost()`                                   | 轉為 `Post` 記錄                             |
| `toArray()`                                  | 轉為 `['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](/zh-TW/packages/laravel-bluesky/basic-client.md)
- [Laravel Bluesky](/zh-TW/packages/laravel-bluesky/index.md)
- [通知頻道 - Laravel Bluesky](/zh-TW/packages/laravel-bluesky/notification.md)
- [Eloquent 的 Scope](/zh-TW/advanced/eloquent-scopes.md)
- [MongoDB](/zh-TW/mongodb.md)
