@bladeberg/editor 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -637
- package/README.npm.md +208 -0
- package/dist-npm/bladeberg.js +206 -193
- package/dist-npm/isolated-block-editor.js +9 -0
- package/dist-npm/style.css +2327 -3113
- package/package.json +2 -4
- package/dist-npm/isolated-block-editor-FqFbPhep.js +0 -61842
package/README.md
CHANGED
|
@@ -1,732 +1,208 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @bladeberg/editor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Gutenberg, standalone.** No WordPress. No Laravel. Just the block editor in your React app.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
BladeBerg wraps [`@automattic/isolated-block-editor`](https://github.com/Automattic/isolated-block-editor) — the same pre-built browser bundle Automattic uses to run Gutenberg outside of wp-admin — and ships it as a lazy-loaded npm package. You get paragraphs, headings, images, columns, embeds, the whole core block library, without installing a single `@wordpress/*` package yourself.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
<a href="https://github.com/BladeBerg/bladeberg/blob/main/LICENSE"><img alt="License: GPL v2" src="https://img.shields.io/badge/License-GPLv2-blue.svg"></a>
|
|
9
|
-
<img alt="PHP 8.2+" src="https://img.shields.io/badge/PHP-8.2%2B-777bb4.svg">
|
|
10
|
-
<img alt="Laravel 10–13" src="https://img.shields.io/badge/Laravel-10%20%7C%2011%20%7C%2012%20%7C%2013-ff2d20.svg">
|
|
11
|
-
</p>
|
|
7
|
+
> Using Laravel? See the full [BladeBerg docs](https://github.com/BladeBerg/bladeberg#readme) for the Composer package, Blade components, PHP rendering, and media API.
|
|
12
8
|
|
|
13
9
|
---
|
|
14
10
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
- [Why BladeBerg?](#why-bladeberg)
|
|
18
|
-
- [How it works](#how-it-works)
|
|
19
|
-
- [Features](#features)
|
|
20
|
-
- [Requirements](#requirements)
|
|
21
|
-
- [Installation](#installation)
|
|
22
|
-
- [Quick start](#quick-start)
|
|
23
|
-
- [Configuration](#configuration)
|
|
24
|
-
- [The `bb:` block format & branding](#the-bb-block-format--branding)
|
|
25
|
-
- [Right-click block menu](#right-click-block-menu)
|
|
26
|
-
- [Storing & rendering content](#storing--rendering-content)
|
|
27
|
-
- [Headless / API usage (SPA, mobile, decoupled)](#headless--api-usage-spa-mobile-decoupled)
|
|
28
|
-
- [Building custom blocks](#building-custom-blocks)
|
|
29
|
-
- [Media manager](#media-manager)
|
|
30
|
-
- [PHP / Facade API](#php--facade-api)
|
|
31
|
-
- [JavaScript API](#javascript-api)
|
|
32
|
-
- [Updating](#updating)
|
|
33
|
-
- [Testing](#testing)
|
|
34
|
-
- [Roadmap](#roadmap)
|
|
35
|
-
- [Contributing](#contributing)
|
|
36
|
-
- [Reporting bugs & requesting features](#reporting-bugs--requesting-features)
|
|
37
|
-
- [Security](#security)
|
|
38
|
-
- [License](#license)
|
|
39
|
-
- [Credits](#credits)
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Why BladeBerg?
|
|
44
|
-
|
|
45
|
-
Laravel is a fantastic application framework, but it has no first-class **content editor**. Teams that need WordPress-style block authoring usually end up with one of three bad options:
|
|
46
|
-
|
|
47
|
-
1. **Run WordPress alongside Laravel** — two apps, two databases, two deploy pipelines, and a headless API glued in between.
|
|
48
|
-
2. **Ship a plain `<textarea>` / markdown field** — fast to build, miserable for non-technical editors.
|
|
49
|
-
3. **Adopt a heavyweight commercial page-builder** — proprietary, hard to theme, and locked to its own data format.
|
|
50
|
-
|
|
51
|
-
BladeBerg was built to remove that compromise. The Gutenberg block editor is the most widely-used block editor on the web, it is open-source (GPL), and — crucially — its editing UI can run completely standalone thanks to [`@automattic/isolated-block-editor`](https://github.com/Automattic/isolated-block-editor). BladeBerg wraps that standalone editor in a Laravel package so you get:
|
|
52
|
-
|
|
53
|
-
- the **exact Gutenberg editing experience** your authors already know,
|
|
54
|
-
- content stored as **portable block HTML** in your own database, on your own terms,
|
|
55
|
-
- a **pure PHP rendering pipeline** (a Blade component + a block parser) with no Node runtime in production,
|
|
56
|
-
- and a **Laravel-native** integration: config, service provider, facade, Artisan installer, Blade components, and your existing filesystem/auth.
|
|
57
|
-
|
|
58
|
-
### How the package came to be
|
|
59
|
-
|
|
60
|
-
BladeBerg started as a spike to answer one question: *“Can the Gutenberg editor be embedded in a Laravel Blade view without dragging in all of WordPress?”* The journey shaped most of the architecture decisions documented below:
|
|
61
|
-
|
|
62
|
-
- **Don't re-bundle `@wordpress/*` yourself.** Early attempts to bundle the individual `@wordpress/block-editor`, `@wordpress/data`, etc. packages hit unsolvable private-API / store-locking errors (`Cannot unlock an object that was not locked before`). The fix was to stop fighting it and ship the **pre-built `isolated-block-editor` browser bundle** as-is, with a thin React-globals + mount layer on top.
|
|
63
|
-
- **Store content under your own brand.** Gutenberg serializes blocks as `<!-- wp:paragraph -->`. BladeBerg rewrites that to a configurable prefix (default `<!-- bb:paragraph -->`) on the way out, and normalizes it back on the way in — so your database never leaks WordPress branding and the format is unmistakably yours.
|
|
64
|
-
- **Re-use Laravel's own storage.** The media manager scans the disk configured by your app's `FILESYSTEM_DISK` instead of inventing a parallel storage system or requiring extra tables.
|
|
65
|
-
|
|
66
|
-
It's still young software (pre-1.0) but it's already usable for real content. See the [roadmap](#roadmap) for what's next.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## How it works
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
74
|
-
│ Browser │
|
|
75
|
-
│ │
|
|
76
|
-
│ <x-bladeberg-editor name="content" /> │
|
|
77
|
-
│ │ │
|
|
78
|
-
│ ▼ │
|
|
79
|
-
│ <textarea data-bladeberg-editor> ← the real form field │
|
|
80
|
-
│ │ │
|
|
81
|
-
│ editor.jsx mounts the isolated-block-editor browser bundle │
|
|
82
|
-
│ on the textarea, hides it, and keeps textarea.value in sync │
|
|
83
|
-
│ │ │
|
|
84
|
-
│ on submit: rewrite <!-- wp:… --> → <!-- bb:… --> │
|
|
85
|
-
└────────┼──────────────────────────────────────────────────────────┘
|
|
86
|
-
│ POST content="<!-- bb:paragraph -->…"
|
|
87
|
-
▼
|
|
88
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
89
|
-
│ Laravel (PHP only) │
|
|
90
|
-
│ │
|
|
91
|
-
│ store $request->input('content') (already bb: prefixed) │
|
|
92
|
-
│ │ │
|
|
93
|
-
│ ▼ │
|
|
94
|
-
│ <x-bladeberg-render :content="$post->content" /> │
|
|
95
|
-
│ │ │
|
|
96
|
-
│ BlockParser normalizes bb: → wp:, parses blocks, │
|
|
97
|
-
│ renders core block HTML + your Blade views for dynamic blocks │
|
|
98
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
The key insight: **the editor is JavaScript, but rendering is pure PHP.** Your production servers never need Node — the compiled editor bundle is published once into `public/vendor/bladeberg/`.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Features
|
|
106
|
-
|
|
107
|
-
- 🧱 **Full Gutenberg editing** — paragraphs, headings, lists, images, columns, quotes, embeds, and the rest of the core block library.
|
|
108
|
-
- 🖊️ **Drop-in Blade components** — `<x-bladeberg-editor>` to edit, `<x-bladeberg-render>` to display.
|
|
109
|
-
- 🏷️ **Configurable block prefix** — your content is saved as `<!-- bb:… -->` (or any prefix you choose), never `wp:`.
|
|
110
|
-
- 🎨 **Re-brandable & themable** — accent color and UI labels are CSS-variable driven; “WordPress/wp:” strings are rebranded in the UI.
|
|
111
|
-
- 🖱️ **Right-click block menu** — restores the context-menu → block Options behaviour the standalone bundle drops.
|
|
112
|
-
- 🗂️ **Optional media manager** — `disabled` / `link` / `select` / `upload` modes, backed by your existing Laravel filesystem disk (no extra tables) or Spatie Media Library.
|
|
113
|
-
- 🧩 **Headless-ready** — a separate `@bladeberg/editor` npm package mounts the editor in any SPA/mobile frontend; an optional render API turns stored content into HTML.
|
|
114
|
-
- ⚙️ **Laravel-native** — service-provider auto-discovery, publishable config, facade, and an Artisan installer.
|
|
115
|
-
- 🚀 **No Node in production** — the editor is pre-built and committed; servers only run PHP.
|
|
116
|
-
- ✅ **Tested** — PHPUnit suite covering the block parser and registry.
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Requirements
|
|
121
|
-
|
|
122
|
-
- PHP **8.2** or higher
|
|
123
|
-
- Laravel **10 / 11 / 12 / 13**
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Installation
|
|
11
|
+
## Install
|
|
128
12
|
|
|
129
13
|
```bash
|
|
130
|
-
|
|
131
|
-
php artisan bladeberg:install
|
|
14
|
+
npm install @bladeberg/editor react react-dom
|
|
132
15
|
```
|
|
133
16
|
|
|
134
|
-
|
|
17
|
+
That's it. The Gutenberg runtime is **bundled inside the package** — you won't hit WordPress dependency hell.
|
|
135
18
|
|
|
136
|
-
|
|
137
|
-
2. Publish the config file to `config/bladeberg.php`.
|
|
138
|
-
3. Print next-step guidance.
|
|
139
|
-
|
|
140
|
-
Prefer plain `vendor:publish`? The unified tag publishes the same set (assets + config):
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
php artisan vendor:publish --tag=bladeberg
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Granular tags are also available: `bladeberg-assets`, `bladeberg-config`, `bladeberg-views`, `bladeberg-migrations`.
|
|
147
|
-
|
|
148
|
-
> **Heads up:** re-run `php artisan bladeberg:install` (or `php artisan vendor:publish --tag=bladeberg-assets --force`) after every package update so the published assets stay in sync with the installed version.
|
|
19
|
+
**Requirements:** React 18, a modern browser, and a bundler that supports ESM (`import`).
|
|
149
20
|
|
|
150
21
|
---
|
|
151
22
|
|
|
152
23
|
## Quick start
|
|
153
24
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```blade
|
|
157
|
-
<form action="{{ route('posts.store') }}" method="POST">
|
|
158
|
-
@csrf
|
|
159
|
-
|
|
160
|
-
<input type="text" name="title" placeholder="Title">
|
|
161
|
-
|
|
162
|
-
<x-bladeberg-editor name="content" />
|
|
163
|
-
|
|
164
|
-
<button type="submit">Publish</button>
|
|
165
|
-
</form>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### 2. Render the stored content on a show page
|
|
169
|
-
|
|
170
|
-
```blade
|
|
171
|
-
<x-bladeberg-render :content="$post->content" />
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### 3. Re-open existing content for editing
|
|
175
|
-
|
|
176
|
-
```blade
|
|
177
|
-
<x-bladeberg-editor name="content" :value="$post->content" />
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
The editor re-hydrates the saved block HTML back into Gutenberg blocks automatically.
|
|
181
|
-
|
|
182
|
-
#### `<x-bladeberg-editor>` props
|
|
183
|
-
|
|
184
|
-
| Prop | Type | Default | Description |
|
|
185
|
-
|---------|----------|----------------|--------------------------------------------------------|
|
|
186
|
-
| `name` | `string` | *(required)* | The form field name posted to your controller. |
|
|
187
|
-
| `value` | `string` | `''` | Existing block HTML to load into the editor. |
|
|
188
|
-
| `id` | `string` | auto-generated | Custom DOM id for the underlying textarea. |
|
|
189
|
-
|
|
190
|
-
A controller is just ordinary Laravel — the content arrives as a normal request field:
|
|
191
|
-
|
|
192
|
-
```php
|
|
193
|
-
public function store(Request $request)
|
|
194
|
-
{
|
|
195
|
-
$request->validate(['content' => ['required', 'string']]);
|
|
196
|
-
|
|
197
|
-
Post::create([
|
|
198
|
-
'title' => $request->string('title'),
|
|
199
|
-
'content' => $request->input('content'), // already <!-- bb:… --> prefixed
|
|
200
|
-
]);
|
|
201
|
-
|
|
202
|
-
return redirect()->route('posts.index');
|
|
203
|
-
}
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Configuration
|
|
209
|
-
|
|
210
|
-
Everything lives in `config/bladeberg.php` (published by the installer).
|
|
211
|
-
|
|
212
|
-
### Editor
|
|
213
|
-
|
|
214
|
-
| Key | Type | Default | Description |
|
|
215
|
-
|---------------------|-----------------|----------|------------------------------------------------------------------------|
|
|
216
|
-
| `block_prefix` | `string` | `'bb'` | Prefix written into block comments on save. `env: BLADEBERG_BLOCK_PREFIX`. |
|
|
217
|
-
| `allowed_blocks` | `array\|null` | `null` | Restrict the available blocks. `null` = allow all registered blocks. |
|
|
218
|
-
| `has_fixed_toolbar` | `bool` | `false` | Pin the block toolbar to the top of the editor. |
|
|
219
|
-
| `align_wide` | `bool` | `true` | Enable wide / full-width alignment for blocks that support it. |
|
|
220
|
-
| `content_storage` | `string` | `'html'` | Storage format. Only `'html'` is supported today. |
|
|
221
|
-
|
|
222
|
-
**Example — restrict the block palette:**
|
|
223
|
-
|
|
224
|
-
```php
|
|
225
|
-
'allowed_blocks' => [
|
|
226
|
-
'core/paragraph',
|
|
227
|
-
'core/heading',
|
|
228
|
-
'core/image',
|
|
229
|
-
'core/list',
|
|
230
|
-
'core/quote',
|
|
231
|
-
],
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### Stylesheets
|
|
235
|
-
|
|
236
|
-
The `styles` array controls which pre-built CSS bundles the editor loads. The defaults are tuned to avoid version conflicts — only change them if you know you need to:
|
|
237
|
-
|
|
238
|
-
| Key | Default | Loads |
|
|
239
|
-
|-----------------|---------|--------------------------------------------------------------------|
|
|
240
|
-
| `core` | `true` | Gutenberg editor chrome (toolbar, canvas, popovers). |
|
|
241
|
-
| `iso` | `true` | `isolated-block-editor` layout shell. |
|
|
242
|
-
| `components` | `false` | `@wordpress/components` (already inside `core.css`). |
|
|
243
|
-
| `blocks_style` | `false` | Per-block **frontend** styles (loaded independently by the renderer). |
|
|
244
|
-
| `blocks_editor` | `false` | Per-block **editor** styles (already inside `core.css`). |
|
|
245
|
-
|
|
246
|
-
### Media
|
|
247
|
-
|
|
248
|
-
See the [Media manager](#media-manager) section for the full breakdown of the `media` block.
|
|
249
|
-
|
|
250
|
-
### Content normalization
|
|
251
|
-
|
|
252
|
-
A server-side safety net that mirrors the client-side `wp:` → `bb:` rewrite for requests that bypass the browser (AJAX, imports). See [Storing & rendering content](#storing--rendering-content).
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## The `bb:` block format & branding
|
|
257
|
-
|
|
258
|
-
Gutenberg serializes blocks with a `wp:` prefix inside HTML comments:
|
|
259
|
-
|
|
260
|
-
```html
|
|
261
|
-
<!-- wp:paragraph --><p>Hello</p><!-- /wp:paragraph -->
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
BladeBerg rewrites this to **your** prefix before the content ever leaves the browser, and normalizes it back internally for parsing — so the round-trip is lossless:
|
|
265
|
-
|
|
266
|
-
```html
|
|
267
|
-
<!-- bb:paragraph --><p>Hello</p><!-- /bb:paragraph -->
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
This happens in two coordinated places:
|
|
271
|
-
|
|
272
|
-
1. **Client (on save)** — a form-submit interceptor in `editor.jsx` (and `window.Bladeberg.getContent()` for AJAX) rewrites `wp:` → your prefix in the textarea value just before the request is sent. This is the only place stored content is branded; nothing on the server rewrites it.
|
|
273
|
-
2. **Server (parsing only)** — `BlockParser` normalizes your prefix back to `wp:` at its entry point purely so it can render the blocks; it never changes what's stored.
|
|
274
|
-
|
|
275
|
-
Change the prefix per project:
|
|
276
|
-
|
|
277
|
-
```php
|
|
278
|
-
// config/bladeberg.php
|
|
279
|
-
'block_prefix' => 'acme', // → <!-- acme:paragraph -->
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
```env
|
|
283
|
-
BLADEBERG_BLOCK_PREFIX=acme
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
The editor UI also rebrands visible “WordPress” / “WP” / `wp:` / `core/` strings (block validation notices, the Code Editor view, etc.) to your branding, and the accent color is driven by CSS variables in `resources/css/editor.scss` (shipped default: BladeBerg red `#e11d1f`).
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
## Right-click block menu
|
|
291
|
-
|
|
292
|
-
The standalone `isolated-block-editor` browser bundle (its final 2.30 release) ships **no** right-click context menu and exposes no data store to open one. BladeBerg restores the expected behaviour with a DOM-only handler:
|
|
293
|
-
|
|
294
|
-
- Right-clicking inside a block selects it and opens that block's existing **Options (⋮)** dropdown — Copy, Duplicate, Move to, Edit as HTML, Group, Lock, Remove, and so on.
|
|
295
|
-
- Right-clicking **outside** any editor block leaves the browser's native menu (spellcheck, etc.) untouched.
|
|
296
|
-
|
|
297
|
-
No configuration required — it's wired automatically when the editor mounts.
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
## Storing & rendering content
|
|
302
|
-
|
|
303
|
-
Store the posted `content` field as-is — it already carries your `bb:` prefix. The rewrite from Gutenberg's `wp:` to your configured prefix happens **entirely on the client**, at save time: the editor's form-submit interceptor (and `window.Bladeberg.getContent()` for AJAX) rewrites the markup before it leaves the browser. There is no server-side middleware to register and nothing rewrites your stored content after the fact.
|
|
304
|
-
|
|
305
|
-
```php
|
|
306
|
-
// Your controller is plain Laravel — content arrives already bb:-prefixed.
|
|
307
|
-
$post->content = $request->input('content');
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
To render it, use the component:
|
|
311
|
-
|
|
312
|
-
```blade
|
|
313
|
-
<x-bladeberg-render :content="$post->content" />
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
> **Importing or pasting existing content?** Handle any prefix conversion on the client. When you load saved content back into the editor via `:value`, BladeBerg converts your prefix to `wp:` on mount so Gutenberg can parse it, then converts it back to your prefix on save. Pasted block markup is serialized by the editor and rewritten on submit the same way.
|
|
317
|
-
|
|
318
|
-
---
|
|
319
|
-
|
|
320
|
-
## Headless / API usage (SPA, mobile, decoupled)
|
|
321
|
-
|
|
322
|
-
The Blade quick-start above is the simplest path, but BladeBerg also works when your frontend is a separate SPA (React, Vue, Next), a mobile webview, or anything that talks to Laravel over JSON. **You do not need a second repository** — the same project ships two artifacts:
|
|
323
|
-
|
|
324
|
-
| Artifact | Registry | Install | Use |
|
|
325
|
-
|----------|----------|---------|-----|
|
|
326
|
-
| `bladeberg/bladeberg` | Packagist | `composer require bladeberg/bladeberg` | PHP: parse, render, dynamic blocks, media + render APIs |
|
|
327
|
-
| `@bladeberg/editor` | npm | `npm i @bladeberg/editor react react-dom` | JS: mount the editor anywhere, headless |
|
|
328
|
-
|
|
329
|
-
The only contract between them is **a string of block HTML with your configured prefix**. The frontend produces it; the backend parses/renders it.
|
|
330
|
-
|
|
331
|
-
```mermaid
|
|
332
|
-
flowchart LR
|
|
333
|
-
spa["SPA / mobile (npm @bladeberg/editor)"] -->|"createEditor() -> getContent()"| str["block-HTML string (bb: prefix)"]
|
|
334
|
-
str -->|"POST JSON"| api["Your Laravel API"]
|
|
335
|
-
api -->|"store as-is"| db[("Database")]
|
|
336
|
-
db -->|"on read"| api
|
|
337
|
-
api -->|"option A: return raw string"| spa
|
|
338
|
-
api -->|"option B: POST /bladeberg/render"| html["rendered HTML"]
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
### Mount the editor (no Blade, no form)
|
|
342
|
-
|
|
343
|
-
```js
|
|
25
|
+
```jsx
|
|
344
26
|
import { createEditor } from '@bladeberg/editor';
|
|
345
27
|
import '@bladeberg/editor/style.css';
|
|
346
28
|
|
|
347
29
|
const editor = await createEditor({
|
|
348
|
-
target: '#editor',
|
|
349
|
-
value:
|
|
350
|
-
blockPrefix: 'bb',
|
|
351
|
-
onChange: (html) => {
|
|
352
|
-
|
|
353
|
-
mode: 'upload', // 'disabled' | 'select' | 'upload'
|
|
354
|
-
apiUrl: '/bladeberg/media',
|
|
355
|
-
csrfToken: window.csrfToken,
|
|
30
|
+
target: '#editor', // selector or DOM element
|
|
31
|
+
value: savedContent, // optional — existing block HTML
|
|
32
|
+
blockPrefix: 'bb', // prefix in stored content (default: bb)
|
|
33
|
+
onChange: (html) => {
|
|
34
|
+
draft = html; // live updates (optional)
|
|
356
35
|
},
|
|
357
36
|
});
|
|
358
37
|
|
|
359
38
|
// When the user saves:
|
|
360
|
-
await fetch('/api/posts', {
|
|
361
|
-
method: '
|
|
39
|
+
await fetch('/api/posts/1', {
|
|
40
|
+
method: 'PUT',
|
|
362
41
|
headers: { 'Content-Type': 'application/json' },
|
|
363
42
|
body: JSON.stringify({ content: editor.getContent() }),
|
|
364
43
|
});
|
|
365
44
|
|
|
366
|
-
// On
|
|
45
|
+
// On route change / unmount:
|
|
367
46
|
editor.destroy();
|
|
368
47
|
```
|
|
369
48
|
|
|
370
|
-
`createEditor()` is async
|
|
371
|
-
|
|
372
|
-
### Render stored content for a headless backend
|
|
373
|
-
|
|
374
|
-
Server-rendered Blade apps just use `<x-bladeberg-render>`. Headless backends have two choices:
|
|
375
|
-
|
|
376
|
-
- **Return the raw string** and render it in the SPA yourself, or
|
|
377
|
-
- **Enable the render API** and let Laravel return finished HTML:
|
|
378
|
-
|
|
379
|
-
```php
|
|
380
|
-
// config/bladeberg.php
|
|
381
|
-
'render_api' => ['enabled' => true, 'middleware' => ['api']],
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
```
|
|
385
|
-
POST /bladeberg/render { "content": "<!-- bb:paragraph -->..." }
|
|
386
|
-
-> { "html": "<div class=\"bb-content\">...</div>" }
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
Or render in-process from any controller/job via the facade:
|
|
390
|
-
|
|
391
|
-
```php
|
|
392
|
-
$html = Bladeberg::render($post->content);
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
> **SSR note:** the package loads a browser-only Gutenberg bundle, so in Next.js/Nuxt import it from a client component (`'use client'` / dynamic `import()` with `ssr: false`).
|
|
49
|
+
`createEditor()` is async — it lazy-loads the Gutenberg runtime on first call (~2 MB, cached after that).
|
|
396
50
|
|
|
397
51
|
---
|
|
398
52
|
|
|
399
|
-
##
|
|
400
|
-
|
|
401
|
-
There are two conceptual ways to extend a Gutenberg-style editor with your own blocks. BladeBerg fully supports one of them today; the other is constrained by the standalone editor bundle. Here's the honest picture:
|
|
402
|
-
|
|
403
|
-
| Approach | "bb-style" — server-rendered | "wp-style" — editor (React) block |
|
|
404
|
-
|----------|------------------------------|-----------------------------------|
|
|
405
|
-
| Renders via | **PHP / Blade** on the server | **React** inside the editor canvas |
|
|
406
|
-
| Editor UI | Re-uses an existing block's editing UI (often `core/html`, `core/group`, or a core block you map) | A bespoke `edit()` / `save()` React component |
|
|
407
|
-
| Registered with | `Bladeberg::registerDynamicBlock()` (PHP) | `registerBlockType()` (JS) |
|
|
408
|
-
| Status | ✅ **Supported now** | 🧪 **Not yet** — see the limitation below |
|
|
409
|
-
| Best for | data-aware output, server logic, Blade reuse | rich custom in-canvas editing |
|
|
410
|
-
|
|
411
|
-
> **Why no custom React blocks yet?** BladeBerg embeds the pre-built `@automattic/isolated-block-editor` browser bundle (v2.30, its final release). That bundle only exposes `window.wp.attachEditor` / `window.wp.detachEditor` — it does **not** expose the `wp.blocks` / `wp.element` / `wp.blockEditor` registration APIs. Without them, custom `edit`/`save` components can't be registered from your app's JS. `window.Bladeberg.registerBlock()` exists and queues calls for the day that becomes possible, but the queue currently does not flush. This is tracked on the [roadmap](#roadmap). **For now, server-rendered blocks below are the supported path.**
|
|
412
|
-
|
|
413
|
-
### bb-style: server-rendered dynamic blocks (recommended)
|
|
414
|
-
|
|
415
|
-
You map a **block name** to a **Blade view**. When `<x-bladeberg-render>` encounters that block in the stored content, it renders your view instead of the raw HTML — ideal for data-aware components (a related-posts list, a pricing table, a CTA pulled from config).
|
|
416
|
-
|
|
417
|
-
The block name can be one you author (e.g. `bladeberg/callout`) or even an existing core block whose output you want to take over.
|
|
418
|
-
|
|
419
|
-
#### Step 1 — map the block name to a view
|
|
420
|
-
|
|
421
|
-
```php
|
|
422
|
-
// app/Providers/AppServiceProvider.php
|
|
423
|
-
use Bladeberg\Facades\Bladeberg;
|
|
424
|
-
|
|
425
|
-
public function boot(): void
|
|
426
|
-
{
|
|
427
|
-
Bladeberg::registerDynamicBlock('bladeberg/callout', 'blocks.callout');
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
#### Step 2 — create the Blade view
|
|
432
|
-
|
|
433
|
-
```blade
|
|
434
|
-
{{-- resources/views/blocks/callout.blade.php --}}
|
|
435
|
-
@php($type = $attributes['type'] ?? 'info')
|
|
436
|
-
|
|
437
|
-
<div class="callout callout--{{ $type }}">
|
|
438
|
-
{!! $innerContent !!}
|
|
439
|
-
</div>
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
Variables available in the view:
|
|
443
|
-
|
|
444
|
-
| Variable | Type | Description |
|
|
445
|
-
|-----------------|-----------|--------------------------------------|
|
|
446
|
-
| `$attributes` | `array` | Block attributes from the editor (the JSON in the block comment). |
|
|
447
|
-
| `$innerContent` | `string` | Rendered inner HTML. |
|
|
448
|
-
| `$innerBlocks` | `Block[]` | Parsed child blocks. |
|
|
449
|
-
| `$block` | `Block` | The full `Block` value object. |
|
|
450
|
-
|
|
451
|
-
The `Block` value object exposes `isNamed()`, `hasInnerBlocks()`, `getAttribute($key, $default)`, and the `$isSelfClosing` flag — handy for rendering nested content:
|
|
452
|
-
|
|
453
|
-
```blade
|
|
454
|
-
{{-- a block that renders its children explicitly --}}
|
|
455
|
-
@foreach ($innerBlocks as $child)
|
|
456
|
-
@if ($child->getAttribute('featured'))
|
|
457
|
-
<strong>{!! $child->innerHTML !!}</strong>
|
|
458
|
-
@else
|
|
459
|
-
{!! $child->innerHTML !!}
|
|
460
|
-
@endif
|
|
461
|
-
@endforeach
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
#### How the block gets into content
|
|
465
|
-
|
|
466
|
-
Until custom React blocks are available, there's no inserter button for a server-only block. You get the block comment into the stored content one of two ways:
|
|
467
|
-
|
|
468
|
-
1. **Code Editor** — switch the editor to Code Editor mode and paste the block comment directly. Gutenberg preserves unrecognized block markup on save, so it round-trips intact:
|
|
469
|
-
|
|
470
|
-
```html
|
|
471
|
-
<!-- bb:bladeberg/callout {"type":"warning"} -->
|
|
472
|
-
<p>Heads up — this is a server-rendered callout.</p>
|
|
473
|
-
<!-- /bb:bladeberg/callout -->
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
2. **Pre-seed it server-side** — set the field's initial value (e.g. from a template) when rendering the editor: `<x-bladeberg-editor name="content" :value="$template" />`.
|
|
477
|
-
|
|
478
|
-
Either way, on display `<x-bladeberg-render>` routes the `bladeberg/callout` block to `resources/views/blocks/callout.blade.php`.
|
|
479
|
-
|
|
480
|
-
### wp-style: custom editor (React) blocks
|
|
53
|
+
## What you get
|
|
481
54
|
|
|
482
|
-
|
|
55
|
+
| Feature | Details |
|
|
56
|
+
|---------|---------|
|
|
57
|
+
| **Full core blocks** | Paragraph, heading, list, image, quote, columns, embeds, etc. |
|
|
58
|
+
| **Portable HTML** | Content serializes to block comments: `<!-- bb:paragraph -->…` |
|
|
59
|
+
| **Branded prefix** | Default `bb:` instead of WordPress's `wp:` — configurable |
|
|
60
|
+
| **Right-click menu** | Block Options (Copy, Duplicate, Remove, …) restored |
|
|
61
|
+
| **Media upload** | Optional — wire to your own API (see below) |
|
|
62
|
+
| **Zero WordPress deps** | Runtime is pre-built and shipped in the tarball |
|
|
483
63
|
|
|
484
64
|
---
|
|
485
65
|
|
|
486
|
-
##
|
|
487
|
-
|
|
488
|
-
BladeBerg ships an optional media manager that wires into Gutenberg's image / file / gallery blocks. It is **disabled by default** and, by design, re-uses the storage your Laravel app already configures.
|
|
489
|
-
|
|
490
|
-
### Modes
|
|
491
|
-
|
|
492
|
-
Set `media.mode` (env: `BLADEBERG_MEDIA_MODE`):
|
|
493
|
-
|
|
494
|
-
| Mode | Behaviour | API routes |
|
|
495
|
-
|------------|--------------------------------------------------------------------------------------------|-------------------------|
|
|
496
|
-
| `disabled` | No media integration. Blocks fall back to a plain URL input. *(default)* | none |
|
|
497
|
-
| `link` | Same as `disabled` server-side (reserved for a future URL-picker). | none |
|
|
498
|
-
| `select` | Browse and insert files already on the disk. **Upload disabled.** | `GET` only |
|
|
499
|
-
| `upload` | Full library: browse existing files **and** upload new ones. | `GET` + `POST` + `DELETE` |
|
|
500
|
-
|
|
501
|
-
### Drivers
|
|
502
|
-
|
|
503
|
-
| Driver | Requires | Notes |
|
|
504
|
-
|--------------|---------------------------------------|------------------------------------------------------------------------------------|
|
|
505
|
-
| `filesystem` | nothing *(default)* | Scans the configured disk directory directly. **No database table or migration.** |
|
|
506
|
-
| `spatie` | `composer require spatie/laravel-medialibrary` | Automatic thumbnails/conversions and multi-disk management. |
|
|
507
|
-
|
|
508
|
-
### Configuration
|
|
509
|
-
|
|
510
|
-
```php
|
|
511
|
-
// config/bladeberg.php
|
|
512
|
-
'media' => [
|
|
513
|
-
'mode' => env('BLADEBERG_MEDIA_MODE', 'disabled'), // disabled|link|select|upload
|
|
514
|
-
'driver' => env('BLADEBERG_MEDIA_DRIVER', 'filesystem'), // filesystem|spatie
|
|
515
|
-
'disk' => env('BLADEBERG_MEDIA_DISK', env('FILESYSTEM_DISK', 'public')),
|
|
516
|
-
'directory' => env('BLADEBERG_MEDIA_DIRECTORY', 'bladeberg'),
|
|
517
|
-
'route_prefix' => 'bladeberg', // → /bladeberg/media
|
|
518
|
-
'middleware' => ['web', 'auth'],
|
|
519
|
-
'max_file_size_kb' => 10240, // 10 MB
|
|
520
|
-
'allowed_mime_types' => [/* images, video, audio, pdf … */],
|
|
521
|
-
'conversions' => [ // spatie driver only
|
|
522
|
-
'thumbnail' => ['width' => 300, 'height' => 300],
|
|
523
|
-
'medium' => ['width' => 768, 'height' => null],
|
|
524
|
-
'large' => ['width' => 1200, 'height' => null],
|
|
525
|
-
],
|
|
526
|
-
],
|
|
527
|
-
```
|
|
66
|
+
## API
|
|
528
67
|
|
|
529
|
-
|
|
68
|
+
### `createEditor(options)`
|
|
530
69
|
|
|
531
|
-
|
|
70
|
+
| Option | Type | Default | Description |
|
|
71
|
+
|--------|------|---------|-------------|
|
|
72
|
+
| `target` | `string \| Element` | *(required)* | CSS selector or element. A `<textarea>` is mounted directly; any other element gets a hidden textarea appended. |
|
|
73
|
+
| `value` | `string` | `''` | Initial block HTML (your configured prefix). |
|
|
74
|
+
| `blockPrefix` | `string` | `'bb'` | Prefix written into block comments on save. |
|
|
75
|
+
| `settings` | `object` | `{}` | Forwarded to Gutenberg's `attachEditor()`. |
|
|
76
|
+
| `media` | `object` | — | `{ mode, apiUrl, csrfToken }` — see [Media](#media). |
|
|
77
|
+
| `branding` | `boolean` | `true` | Rebrand "WordPress" strings in the UI. |
|
|
78
|
+
| `contextMenu` | `boolean` | `true` | Restore right-click block menu. |
|
|
79
|
+
| `onChange` | `(html) => void` | — | Called when content changes (polled every 300 ms). |
|
|
532
80
|
|
|
533
|
-
|
|
534
|
-
php artisan storage:link
|
|
535
|
-
```
|
|
81
|
+
**Returns** `Promise<EditorHandle>`:
|
|
536
82
|
|
|
537
|
-
```
|
|
538
|
-
|
|
83
|
+
```js
|
|
84
|
+
editor.getContent() // current block HTML (prefixed)
|
|
85
|
+
editor.onChange(fn) // subscribe; returns unsubscribe fn
|
|
86
|
+
editor.destroy() // detach editor + stop listeners
|
|
87
|
+
editor.textarea // underlying textarea element
|
|
539
88
|
```
|
|
540
89
|
|
|
541
|
-
###
|
|
90
|
+
### `registerBlock(name, settings)`
|
|
542
91
|
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
|
|
546
|
-
php artisan migrate
|
|
547
|
-
php artisan storage:link
|
|
548
|
-
```
|
|
92
|
+
```js
|
|
93
|
+
import { registerBlock } from '@bladeberg/editor';
|
|
549
94
|
|
|
550
|
-
|
|
551
|
-
'media' => ['mode' => 'upload', 'driver' => 'spatie'],
|
|
95
|
+
registerBlock('my-plugin/callout', { /* block settings */ });
|
|
552
96
|
```
|
|
553
97
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
When `mode` is `select` or `upload`, the following JSON routes are registered under `media.route_prefix`:
|
|
557
|
-
|
|
558
|
-
| Method | URL | Modes | Description |
|
|
559
|
-
|----------|---------------------------|------------------|--------------------------------------|
|
|
560
|
-
| `GET` | `/bladeberg/media` | select, upload | List media (paginated, searchable). |
|
|
561
|
-
| `GET` | `/bladeberg/media/{id}` | select, upload | Single attachment. |
|
|
562
|
-
| `POST` | `/bladeberg/media` | upload only | Upload a file (`multipart/form-data`). |
|
|
563
|
-
| `DELETE` | `/bladeberg/media/{id}` | upload only | Delete attachment + file. |
|
|
564
|
-
|
|
565
|
-
Query parameters for the list endpoint: `page`, `per_page`, `search`, `media_type` (`image` / `video` / `audio` / `application`).
|
|
98
|
+
> **Note:** The current isolated-block-editor bundle (v2.30) does not expose `window.wp.blocks`, so custom React blocks are queued but not registered yet. Use server-rendered blocks with [BladeBerg's PHP package](https://github.com/BladeBerg/bladeberg) instead.
|
|
566
99
|
|
|
567
100
|
---
|
|
568
101
|
|
|
569
|
-
##
|
|
102
|
+
## Content format
|
|
570
103
|
|
|
571
|
-
|
|
572
|
-
use Bladeberg\Facades\Bladeberg;
|
|
104
|
+
Gutenberg saves blocks as HTML comments:
|
|
573
105
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
Bladeberg::isDynamicBlock('bladeberg/hero'); // true
|
|
577
|
-
Bladeberg::hasBlock('bladeberg/hero'); // alias of isDynamicBlock()
|
|
578
|
-
Bladeberg::getDynamicBlockView('bladeberg/hero'); // 'blocks.hero'
|
|
579
|
-
Bladeberg::getRegisteredBlocks(); // ['bladeberg/hero' => 'blocks.hero', …]
|
|
580
|
-
|
|
581
|
-
// Render stored block content to HTML (same output as <x-bladeberg-render>).
|
|
582
|
-
// Useful for JSON render endpoints, queued jobs, feeds, sitemaps, etc.
|
|
583
|
-
Bladeberg::render($post->content); // '<div class="bb-content">…</div>'
|
|
106
|
+
```html
|
|
107
|
+
<!-- bb:paragraph --><p>Hello world</p><!-- /bb:paragraph -->
|
|
584
108
|
```
|
|
585
109
|
|
|
586
|
-
|
|
110
|
+
The `bb:` prefix (configurable via `blockPrefix`) keeps your database free of WordPress branding. On load, BladeBerg converts it back to `wp:` internally so Gutenberg can parse it, then converts it back on save.
|
|
587
111
|
|
|
588
112
|
---
|
|
589
113
|
|
|
590
|
-
##
|
|
591
|
-
|
|
592
|
-
### Blade integration (global `window.Bladeberg`)
|
|
114
|
+
## Media (optional)
|
|
593
115
|
|
|
594
|
-
|
|
116
|
+
Wire the editor to your own upload API:
|
|
595
117
|
|
|
596
118
|
```js
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
119
|
+
const editor = await createEditor({
|
|
120
|
+
target: '#editor',
|
|
121
|
+
media: {
|
|
122
|
+
mode: 'upload', // 'disabled' | 'select' | 'upload'
|
|
123
|
+
apiUrl: '/api/media', // your JSON media endpoints
|
|
124
|
+
csrfToken: getCsrfToken(), // optional
|
|
125
|
+
},
|
|
126
|
+
});
|
|
604
127
|
```
|
|
605
128
|
|
|
606
|
-
|
|
129
|
+
If you're using [BladeBerg for Laravel](https://github.com/BladeBerg/bladeberg), the backend ships ready-made routes at `/bladeberg/media`.
|
|
607
130
|
|
|
608
|
-
|
|
609
|
-
import { createEditor, registerBlock } from '@bladeberg/editor';
|
|
610
|
-
import '@bladeberg/editor/style.css';
|
|
131
|
+
---
|
|
611
132
|
|
|
612
|
-
|
|
613
|
-
editor.getContent(); // branded block-HTML string
|
|
614
|
-
editor.destroy(); // detach + stop listeners
|
|
615
|
-
```
|
|
133
|
+
## SSR / Next.js / Nuxt
|
|
616
134
|
|
|
617
|
-
|
|
135
|
+
The editor is **browser-only**. Import it from a client component:
|
|
618
136
|
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
// The shipped isolated-block-editor build does not expose window.wp.blocks,
|
|
622
|
-
// so this call is queued but never flushes. It is kept as forward-compatible
|
|
623
|
-
// API surface; use server-rendered blocks instead (see "Building custom blocks").
|
|
624
|
-
registerBlock('bladeberg/callout', { /* block settings */ });
|
|
625
|
-
```
|
|
137
|
+
```jsx
|
|
138
|
+
'use client';
|
|
626
139
|
|
|
627
|
-
|
|
140
|
+
import { useEffect, useRef } from 'react';
|
|
628
141
|
|
|
629
|
-
|
|
142
|
+
export default function PostEditor({ content }) {
|
|
143
|
+
const ref = useRef(null);
|
|
630
144
|
|
|
631
|
-
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
let editor;
|
|
147
|
+
import('@bladeberg/editor').then(({ createEditor }) => {
|
|
148
|
+
createEditor({ target: ref.current, value: content }).then((e) => { editor = e; });
|
|
149
|
+
});
|
|
150
|
+
return () => editor?.destroy();
|
|
151
|
+
}, [content]);
|
|
632
152
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
php artisan bladeberg:install # or: vendor:publish --tag=bladeberg-assets --force
|
|
636
|
-
php artisan view:clear
|
|
637
|
-
php artisan config:clear
|
|
153
|
+
return <div ref={ref} />;
|
|
154
|
+
}
|
|
638
155
|
```
|
|
639
156
|
|
|
640
|
-
|
|
157
|
+
Don't forget `import '@bladeberg/editor/style.css'` somewhere in your client bundle.
|
|
641
158
|
|
|
642
159
|
---
|
|
643
160
|
|
|
644
|
-
##
|
|
161
|
+
## Rendering stored content
|
|
645
162
|
|
|
646
|
-
|
|
647
|
-
# Package unit tests (run from packages/bladeberg/)
|
|
648
|
-
composer test
|
|
163
|
+
This npm package is **editor-only**. To turn block HTML into visitor-facing HTML you need a renderer:
|
|
649
164
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
165
|
+
- **[BladeBerg Laravel package](https://github.com/BladeBerg/bladeberg)** — `<x-bladeberg-render>`, `Bladeberg::render()`, or `POST /bladeberg/render`
|
|
166
|
+
- **Your own backend** — parse `<!-- bb:… -->` comments and render block HTML yourself
|
|
167
|
+
- **Return raw block HTML** to the frontend and render client-side
|
|
653
168
|
|
|
654
169
|
---
|
|
655
170
|
|
|
656
|
-
##
|
|
171
|
+
## How it works
|
|
657
172
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
173
|
+
```
|
|
174
|
+
Your React app
|
|
175
|
+
│
|
|
176
|
+
├─ import { createEditor } from '@bladeberg/editor'
|
|
177
|
+
├─ import '@bladeberg/editor/style.css'
|
|
178
|
+
│
|
|
179
|
+
▼
|
|
180
|
+
createEditor() lazy-loads isolated-block-editor.js (bundled in the package)
|
|
181
|
+
│
|
|
182
|
+
▼
|
|
183
|
+
window.wp.attachEditor(textarea) ← full Gutenberg UI
|
|
184
|
+
│
|
|
185
|
+
▼
|
|
186
|
+
editor.getContent() → "<!-- bb:paragraph -->…" → POST to your API
|
|
187
|
+
```
|
|
663
188
|
|
|
664
|
-
|
|
189
|
+
No `@wordpress/block-editor`, no `@wordpress/data`, no dependency resolver nightmares. The hard part is already done.
|
|
665
190
|
|
|
666
191
|
---
|
|
667
192
|
|
|
668
|
-
##
|
|
669
|
-
|
|
670
|
-
Contributions are very welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide. In short:
|
|
193
|
+
## Development (maintainers)
|
|
671
194
|
|
|
672
195
|
```bash
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
npm
|
|
677
|
-
npm run build # build the editor bundle into dist/
|
|
678
|
-
composer test # run the PHP test suite
|
|
196
|
+
cd packages/bladeberg
|
|
197
|
+
npm install
|
|
198
|
+
npm run build:npm # → dist-npm/bladeberg.js + style.css + isolated-block-editor.js
|
|
199
|
+
npm pack # smoke-test the tarball locally
|
|
679
200
|
```
|
|
680
201
|
|
|
681
|
-
|
|
682
|
-
2. Add or update tests for your change.
|
|
683
|
-
3. Run `composer test` (and `npm run build` if you touched any JS).
|
|
684
|
-
4. Note your change under `[Unreleased]` in [CHANGELOG.md](CHANGELOG.md).
|
|
685
|
-
5. Open a pull request with a clear before/after description.
|
|
686
|
-
|
|
687
|
-
---
|
|
688
|
-
|
|
689
|
-
## Reporting bugs & requesting features
|
|
690
|
-
|
|
691
|
-
Please use the **GitHub issue tracker**: <https://github.com/BladeBerg/bladeberg/issues>
|
|
692
|
-
|
|
693
|
-
When reporting a bug, include:
|
|
694
|
-
|
|
695
|
-
- PHP and Laravel versions,
|
|
696
|
-
- the installed BladeBerg version,
|
|
697
|
-
- clear steps to reproduce,
|
|
698
|
-
- expected vs. actual behaviour,
|
|
699
|
-
- any console errors (browser dev-tools) and relevant `storage/logs/laravel.log` output.
|
|
700
|
-
|
|
701
|
-
For feature requests, describe the problem you're trying to solve — not just the solution you have in mind.
|
|
702
|
-
|
|
703
|
-
---
|
|
704
|
-
|
|
705
|
-
## Security
|
|
706
|
-
|
|
707
|
-
If you discover a security vulnerability, please **do not** open a public issue. Email `security@bladeberg.dev` (or open a private GitHub security advisory) so it can be addressed before disclosure.
|
|
202
|
+
Publish happens via GitHub Actions on `v*` tags. See [RELEASE.md](RELEASE.md).
|
|
708
203
|
|
|
709
204
|
---
|
|
710
205
|
|
|
711
206
|
## License
|
|
712
207
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
This license was chosen deliberately: BladeBerg embeds the WordPress/Gutenberg block editor, which is itself GPL-2.0-or-later. Distributing under the same license keeps BladeBerg fully compatible with the code it builds on.
|
|
716
|
-
|
|
717
|
-
```
|
|
718
|
-
Copyright (C) 2026 BladeBerg
|
|
719
|
-
|
|
720
|
-
This program is free software; you can redistribute it and/or modify it under
|
|
721
|
-
the terms of the GNU General Public License as published by the Free Software
|
|
722
|
-
Foundation; either version 2 of the License, or (at your option) any later
|
|
723
|
-
version.
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
---
|
|
727
|
-
|
|
728
|
-
## Credits
|
|
729
|
-
|
|
730
|
-
- [Gutenberg](https://github.com/WordPress/gutenberg) and [`@automattic/isolated-block-editor`](https://github.com/Automattic/isolated-block-editor) — the editor BladeBerg stands on.
|
|
731
|
-
- [Laravel](https://laravel.com) — the framework that makes it feel at home.
|
|
732
|
-
- Everyone who [contributes](CONTRIBUTING.md) to BladeBerg.
|
|
208
|
+
GPL-2.0-or-later — same as Gutenberg. See [LICENSE](LICENSE).
|