# @joydea/rich-editor

Notion 風の Markdown リッチエディタ（TipTap / ProseMirror 製）。ツールバー無しで、行頭の「＋」や
「/」でブロック挿入、選択でバブルメニュー、Markdown ショートカット、画像のドラッグリサイズ、GFM テーブル、
@メンション、下書き自動保存（localStorage 復元）に対応します。

- **フレームワーク非依存のコア**（`textarea` を渡すだけ）＋ **Laravel(Blade) 同梱**（`<x-rich-editor>`）
- 保存形式は **Markdown**（`textarea` の値に書き戻される）
- relay / backlog-clone から切り出した独立パッケージ。**backlog-clone 本体は変更していません**

---

## 使い方の基本 — まずは clone

現状 **Packagist / npm へは未公開** です。とりあえずは **このリポジトリを clone して** 使ってください
（パス参照＝Laravel／ビルド済み `dist/` の読み込み＝素のJS）。将来は git タグ参照や Packagist / npm 公開へ
移行できます（末尾「配布を楽にするなら」）。

```bash
git clone <repo-url> rich-editor      # push 先は任意（社内 Forgejo / GitHub など）
cd rich-editor
npm install && npm run build          # dist/ を生成・更新
```

> `dist/`（ビルド済み JS/CSS）は **リポジトリにコミット済み** です。Laravel でパス参照するだけなら
> clone しただけで動きます（`npm run build` はコードを改造したときや `dist/` を最新化したいときだけ）。

---

## A. Laravel プロジェクトで使う

### 1. clone した場所をパス参照する

利用側プロジェクトの `composer.json` に、clone したディレクトリを `path` リポジトリとして足します。

```jsonc
{
  "repositories": [
    { "type": "path", "url": "../rich-editor" }   // ← clone した場所に合わせる（絶対パス可）
  ]
}
```

```bash
composer require joydea/rich-editor:@dev              # path 参照は dev 版なので @dev

php artisan vendor:publish --tag=rich-editor-assets   # dist を public/vendor/rich-editor へコピー
# （任意）php artisan vendor:publish --tag=rich-editor-config
# （任意）php artisan vendor:publish --tag=rich-editor-views
```

ServiceProvider は Laravel のパッケージ自動検出で読み込まれます。
パッケージを再ビルドしたら `php artisan vendor:publish --tag=rich-editor-assets --force` で再コピーします。

### 2. Blade で置く

```blade
{{-- 最小 --}}
<x-rich-editor name="description" :value="old('description', $issue->description)" />

{{-- 画像アップロード＋メンション＋下書きキー --}}
<x-rich-editor
    name="body"
    :value="old('body')"
    placeholder="説明を入力…"
    :upload-url="route('images.store', $project)"
    :mentions="$members"                {{-- [['name'=>'田中','avatar'=>'/a.png','initials'=>'田'], ...] --}}
    min-height="180px"
    :draft-key="'issue:'.$project->id.':'.($issue->id ?? 'new').':body'"
/>
```

`config('rich-editor.inject_assets')` が `true`（既定）なら、コンポーネントの初回描画で CSS/JS を
1 度だけ読み込みます。`@vite` などで自前管理したい場合は `false` にしてください。

### 画像アップロードの契約

`upload-url` へ `multipart/form-data`（フィールド名 `file`）を CSRF 付き POST します。サーバは
`{ "url": "...", "alt": "..." }` を JSON で返してください（`meta[name=csrf-token]` を自動送出）。

---

## B. 素の JS / 他フレームワークで使う

### B-1. ビルド済み `dist/` をコピーする（ビルド環境不要）

clone 済みの `dist/rich-editor.js` と `dist/rich-editor.css` を、使いたいプロジェクトの公開ディレクトリ
（例 `/assets`）へコピーして読み込むだけ。

```html
<link rel="stylesheet" href="/assets/rich-editor.css">
<script type="module" src="/assets/rich-editor.js"></script>

<!-- data-rich-editor を付けた textarea は自動でマウントされる -->
<textarea data-rich-editor placeholder="ここに入力…"></textarea>
```

手動マウントや設定注入は `window.RichEditor.mount(...)` で:

```html
<script type="module">
  // rich-editor.js の評価後に window.RichEditor が生える
  window.addEventListener('DOMContentLoaded', () => {
    window.RichEditor.mount('#editor', {
      minHeight: '180px',
      upload: async (file) => ({ url: await myUploader(file), alt: file.name }),
      mentions: [{ name: '田中 博人', initials: '田' }],
    });
  });
</script>
```

自動マウントを止めたいときは `<html data-rich-editor-manual>` を付けます。

### B-2. バンドラ（Vite / webpack / React / Vue）から使う

clone したディレクトリをローカル依存として入れます（npm 未公開なのでパス指定）。

```bash
npm i ../rich-editor          # clone した場所に合わせる（将来は npm i @joydea/rich-editor）
```

```js
import { mountRichEditor } from '@joydea/rich-editor';
import '@joydea/rich-editor/css'; // 同梱テーマ（トークンは後述の通り上書き可）

mountRichEditor('#editor', {
  placeholder: 'ここに入力…',
  minHeight: '180px',
  // 画像アップロード（未指定 or uploadUrl 未指定なら「画像」ブロックは非表示）
  upload: async (file) => {
    const url = await myUploader(file);
    return { url, alt: file.name };
  },
  // uploadUrl: '/images',      // ← upload を渡さない場合の Laravel 風フォールバック（CSRF付きPOST）
  mentions: [{ name: '田中 博人', initials: '田' }],
  // 「関連ファイル」ブロック（渡した時だけ表示）。自前ピッカーで選んだファイルを insert() で挿入
  onPickFile: (insert) => openMyPicker((f) => insert({
    name: f.name, isImage: f.isImage, previewUrl: f.previewUrl, downloadUrl: f.url,
  })),
});
```

React / Vue でも「ref で取った `<textarea>` を `mountRichEditor` に渡し、unmount 時に `el._richEditor.destroy()`」
の薄いラッパーを書けばそのまま使えます（コアはバニラ）。

### 公開 API

| API | 説明 |
|---|---|
| `mountRichEditor(target, options)` | セレクタ or textarea をマウント。返り値は TipTap `Editor` |
| `initRichEditors(root?)` | `[data-rich-editor]` をまとめてマウント |
| `initDrafts()` | 下書きの掃除・送信済み判定・送信フック（マウント前に一度） |
| `clearDraft(el)` / `clearDraftByKey(key)` / `draftKeyFor(el)` | 下書き操作 |

マウント後、`textarea._richEditor = { getMarkdown(), setMarkdown(v), focus(), destroy() }` が生えます。

---

## テーマ（見た目の差し替え）

デザイントークンは `--re-*` に名前空間化されています。`:root`（またはエディタの親）で上書きするだけ。

```css
:root {
  --re-accent: #2563eb;
  --re-accent-ink: #1e40af;
  --re-accent-soft: #e0e7ff;
  --re-surface: #fff;
  --re-surface-2: #f5f5f5;
  --re-ink: #111827;
  --re-line: #e5e7eb;
  /* ほか: --re-paper --re-muted --re-faint --re-line-2 --re-accent-press --re-danger --re-danger-ink --re-font-mono */
}
```

---

## Markdown 仕様メモ（サーバ側レンダリング）

- **画像の幅**は標準 Markdown に無いため、タイトル枠へ `![alt](url "width=300")` として埋めます。
  表示側（HTML 変換）で `title=width=NNN` を `style="width:NNNpx"` に読み替えてください。
- **テーブル**は GFM のパイプ表（`| … |`）で出力します。GFM 対応の Markdown レンダラで表示できます。
- 出力 Markdown の HTML 化・サニタイズは利用側の責務です（例: Laravel なら `Str::markdown(..., ['html_input' => 'strip'])`）。

---

## 動作確認・開発

```bash
npm install
npm run build          # dist/rich-editor.js (+ .umd.cjs) と dist/rich-editor.css を出力
open demo/index.html   # ビルド後、ブラウザで動作確認（自動マウント／画像・メンション／テーマ差し替えの例）
```

ディレクトリ構成: `resources/js`（コア）・`resources/css`（同梱テーマ）・`src`（Laravel グルー）・
`resources/views`（Blade）・`dist`（ビルド成果物＝コミット対象）。

---

## 将来：配布を楽にするなら

clone 運用から一歩進めたくなったら、以下のいずれかへ移行できます（今は不要）。

| やること | 効果 |
|---|---|
| git リモートへ push ＋ `git tag v0.1.0` | 他プロジェクトから `type: vcs` 参照 / `npm i git+…#v0.1.0` |
| Packagist 公開（or 社内 Satis） | `composer require joydea/rich-editor` だけで入る |
| npm publish（or 社内レジストリ） | `npm i @joydea/rich-editor` だけで入る |
