Markdown Editor
PandaPanel\Forms\Components\MarkdownEditor menyimpan formatted text dalam format Markdown. Toolbar hanya memasukkan karakter yang sebenarnya juga dapat diketik user secara manual, sedangkan preview hanya merender salinan value — keduanya tidak menulis ulang data yang akan disubmit. Gunakan field ini ketika sebuah column menyimpan prose yang nantinya akan dirender. Gunakan RichEditor jika column harus menyimpan HTML, dan Code Editor jika value berisi source code.
Contoh minimal
use PandaPanel\Forms\Components\MarkdownEditor;
use PandaPanel\Forms\FormSchema;
FormSchema::make()->schema([
MarkdownEditor::make('body')
->rows(16)
->maxLength(20_000)
->columnSpanFull(),
]);2
3
4
5
6
7
8
9
Field ini cukup menggunakan column text. Stored value adalah karakter yang diketik user secara persis.
Mengapa Markdown, bukan HTML
Markdown lebih aman disimpan dibanding HTML karena nilainya inert sampai sesuatu merendernya. Proses rendering itulah tempat escaping harus dilakukan. Itulah alasan field ini tidak melakukan sanitization, sedangkan RichEditor melakukannya: risikonya berasal dari storage format, bukan dari editor. Jika sebuah column diubah dari satu editor ke editor lain, responsibility sanitization ikut berpindah.
Method
public function toolbar(array $buttons): self // list<string>
public function maxLength(int $length): self // default: null, clamped to >= 1
public function rows(int $rows): self // default: 10, clamped to >= 12
3
| Method | Default | Dampak |
|---|---|---|
toolbar() | sepuluh tombol default di bawah | menentukan tombol yang dirender dan urutannya |
maxLength() | null | menambahkan max:n pada rules dan maxlength pada textarea |
rows() | 10 | menentukan rows pada textarea |
use PandaPanel\Forms\Components\MarkdownEditor;
MarkdownEditor::make('summary')
->toolbar(['bold', 'italic', 'link', 'preview'])
->rows(6)
->maxLength(500)
->helperText('Shown in listings. Keep it to a couple of sentences.');2
3
4
5
6
7
Tombol toolbar
Daftar default, sesuai urutan:
['bold', 'italic', 'strike', 'link', 'heading', 'bulletList', 'orderedList', 'blockquote', 'code', 'preview']Setiap nama dipetakan ke behavior yang sudah dikenal editor saat build. Tidak ada registry yang dapat diperluas; nama yang tidak dikenali tidak merender apa pun.
| Nama | Label | Yang dimasukkan |
|---|---|---|
bold | B | ** mengelilingi selection |
italic | I | * mengelilingi selection |
strike | S | ~~ mengelilingi selection |
code | </> | backtick mengelilingi selection |
link | Link | [ … ](https://) |
heading | H | ## sebelum selection |
bulletList | • List | - sebelum selection |
orderedList | 1. List | 1. sebelum selection |
blockquote | ❝ | > sebelum selection |
preview | Preview / Write | toggle preview pane, dirender di sisi kanan toolbar |
Setelah syntax dimasukkan, caret dikembalikan ke area selection sehingga user dapat melanjutkan mengetik di lokasi yang sama, bukan dipindahkan ke akhir syntax.
Berikan array kosong jika hanya membutuhkan textarea tanpa toolbar:
use PandaPanel\Forms\Components\MarkdownEditor;
MarkdownEditor::make('notes')->toolbar([]);2
3
Validasi
use PandaPanel\Forms\Components\MarkdownEditor;
use PandaPanel\Forms\FormSchema;
FormSchema::make()
->schema([MarkdownEditor::make('body')->required()->maxLength(5000)])
->validationRules();
// ['body' => ['required', 'string', 'max:5000']]2
3
4
5
6
7
8
max: pada string menghitung karakter. Angka yang sama dikirim ke browser sebagai hint validation.max dan attribute maxlength, sehingga limit terlihat sebelum round trip dan tetap diperiksa ulang oleh server sebagai authority.
Hydration
protected function castForForm(mixed $value): ?stringString diteruskan tanpa perubahan; nilai lain menjadi null. Tidak ada konversi pada kedua arah — yang diketik adalah yang disimpan, dan yang disimpan adalah yang diedit.
Preview
Preview pane dirender oleh resources/js/panel/forms/markdown.ts, renderer kecil yang sengaja dibatasi. Tujuannya agar tombol Preview dapat menunjukkan representasi yang sesuai dengan apa yang diketik tanpa menambahkan parser dependency yang belum disetujui application.
Renderer menangani headings, blockquotes, fenced code, lists, paragraphs, dan inline marks yang dapat dibuat toolbar. Syntax lain ditampilkan sebagai teks biasa.
Dua behavior berikut sangat penting:
- Setiap karakter di-HTML-escape terlebih dahulu. Hanya tag yang secara eksplisit ditambahkan renderer yang dapat menjadi markup setelah proses tersebut. Inilah yang membuat penggunaan
v-htmlaman karena author input tidak lagi dapat disalahartikan sebagai markup. - Link hanya menerima
http,https,mailto, dan root-relative URL.javascript:adalah jalur yang dapat membuat teks menjadi behavior, sehingga link dengan scheme tersebut tetap ditampilkan sebagai literal text.
Semua behavior preview tidak memengaruhi value yang disimpan. Preview adalah view dari value, bukan conversion step.
Merender stored value
Field hanya menyimpan Markdown. PandaBear tidak menentukan bagaimana public side application merender value tersebut; itulah responsibility application dan tempat escaping dilakukan. Dua pendekatan yang sesuai:
- render di server menggunakan Markdown package pilihan application lalu pastikan output ditangani dengan aman; atau
- tampilkan pada infolist sebagai teks, seperti behavior Panel sendiri.
Preview renderer bawaan adalah concern editor dan tidak diekspor untuk general-purpose rendering.
Data yang dikirim ke frontend
interface MarkdownEditorFieldDefinition extends BaseFieldDefinition {
type: 'markdown_editor';
toolbar: string[];
maxLength: number | null;
rows: number;
}2
3
4
5
6
Hal yang perlu diperhatikan
Tidak ada sanitization pada stored value. Ini disengaja karena Markdown dianggap inert text, tetapi value masih dapat mengandung raw HTML jika user mengetiknya. Renderer yang membolehkan raw HTML akan merendernya. Lakukan escaping saat render atau gunakan renderer yang tidak meneruskan raw HTML.
toolbar() mengganti seluruh list, bukan menambahkannya. toolbar(['preview']) menghasilkan toolbar yang hanya memiliki Preview. Tuliskan seluruh urutan yang memang diinginkan.
Nama tombol yang tidak dikenal diabaikan tanpa error. Typo hanya membuat tombol hilang.
maxLength() menghitung karakter, bukan byte. Laravel max: pada string menghitung karakter, sedangkan browser maxlength menghitung UTF-16 code units. Keduanya dapat berbeda untuk karakter astral seperti emoji; hasil server tetap menjadi keputusan akhir.
Editor yang dikosongkan mengirim '', bukan null. nullable hanya mengabaikan null asli. Rule string tetap menerima empty string dan value tersebut mencapai column. Normalisasi melalui mutateUsing() jika empty string harus disimpan sebagai null.
rows() hanya menentukan initial height. User masih dapat melakukan resize textarea; field tidak mengunci tinggi control.
Lihat juga
- Code Editor — source text monospace dengan rule
json - Text — textarea biasa jika formatting tidak diperlukan
- Rich Editor — HTML yang disanitasi sebelum disimpan
- Validation
- Infolist Entries — menampilkan stored text
- Forms and Schemas