A Rich Text Editor plugin for Filament Forms.
Install the package via composer
composer require awcodes/scribble
php artisan vendor:publish --tag="scribble-config"
php artisan vendor:publish --tag="scribble-translations"
Important
If you have not set up a custom theme and are using a Panel follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.
Import the plugin's stylesheet (if not already included) into your theme's css file.
@import '/vendor/awcodes/scribble/resources/css/editor.css';
@import '/vendor/awcodes/scribble/resources/css/entry.css';
Add the plugin's views to your tailwind.config.js
file.
content: [
'./vendor/awcodes/scribble/resources/**/*{.blade.php,.svelte}',
]
Rebuild your custom theme.
npm run build
Scribble stores its content as JSON data in a single column on your model. So, it is vital that you cast the column to an array or json object in your model.
protected $casts = [
'content' => 'array', // or 'json'
];
It is also recommended to make the column alongText
type in your migration. However, this is not required and if you know you will not need a large amount of data you can use a text
or mediumText
type as well. Just be aware that the content can grow rather quickly.
$table->longText('content')->nullable();
use Awcodes\Scribble\ScribbleEditor;
public function form(Form $form): Form
{
return $form
->schema([
ScribbleEditor::make('content')
])
}
use Awcodes\Scribble\ScribbleEntry;
public function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
ScribbleEntry::make('content')
]);
}
In the boot
method of a ServiceProvider you can set the default configuration for all instances of the editor with the configureUsing
method.
use Awcodes\Scribble\ScribbleEditor;
use Awcodes\Scribble\Pofiles\MinimalProfile;
ScribbleEditor::configureUsing(function (ScribbleEditor $scribble) {
$scribble
->renderToolbar()
->profile(MinimalProfile::class)
});
Manually, creating menu configurations for each instance of the editor can be cumbersome. To alleviate this, you can create a profile class that defines the tools for the bubble, suggestion, and toolbar menus. You can then apply the profile to the editor using the profile
method.
namespace App\ScribbleProfiles;
use Awcodes\Scribble\Facades\ScribbleFacade;
use Awcodes\Scribble\ScribbleProfile;
class Minimal extends ScribbleProfile
{
public static function bubbleTools(): array
{
return ScribbleFacade::getTools([
'paragraph',
'bold',
'italic',
'link',
'bullet-list',
'ordered-list',
])->toArray();
}
public static function suggestionTools(): array
{
return [];
}
public static function toolbarTools(): array
{
return ScribbleFacade::getTools([
'paragraph',
'bold',
'italic',
'link',
'bullet-list',
'ordered-list',
])->toArray();
}
}
use App\ScribbleProfiles\Minimal;
Scribble::configureUsing('content')
->profile(Mimimal::class)
You can scaffold out a new profile class using the make:scribble-profile
command and following the prompts.
php artisan make:scribble-profile
Should you need to provide styles to the editor for custom blocks or tools, you can use the customStyles
method to provide a path to a CSS file.
Scribble::make('content')
->customStyles('path/to/custom.css')
Command tools are used to insert content into the editor using Tiptap commands. The Bold
and Italic
tools are examples of this. This is also the default tool type.
use Awcodes\Scribble\ScribbleTool;
class Bold extends ScribbleTool
{
protected function setUp(): void
{
$this
->icon('scribble-bold')
->label('Bold')
->extension('bold')
->active(extension: 'bold')
->commands([
$this->makeCommand(command: 'toggleBold'),
// or
['command' => 'toggleBold', 'arguments' => null],
]);
}
}
Static Blocks are a tool type that can be used to insert a static blade view into the editor. These are useful for inserting placeholder content that can be rendered out to a different view in your HTML. For instance, a block that represents a list of FAQs that when rendered on the front-end will display a list of FAQs from the database.
$editorView
is optional but can be useful in the case that you need to provide a custom editor view for the block. And a different rendering view for the output.
use Awcodes\Scribble\ScribbleTool;
use Awcodes\Scribble\Enums\ToolType;
class FaqsList extends ScribbleTool
{
protected function setUp(): void
{
$this
->icon('heroicon-o-question-mark-circle')
->label('FAQs List')
->type(ToolType::StaticBlock)
->editorView('scribble-tools.faqs-list-editor')
->renderedView('scribble-tools.faqs-list');
}
}
{{-- scribble.static-block-editor --}}
<div class="p-4 bg-gray-800 rounded-lg">
<p>This is a placeholder. FAQ list will be rendered on output.</p>
</div>
{{-- scribble.static-block --}}
<div class="p-4 bg-gray-800 rounded-lg">
@foreach ($faqs as $faq)
<div class="mb-4">
<h3 class="text-lg font-bold">{{ $faq->question }}</h3>
<p>{{ $faq->answer }}</p>
</div>
@endforeach
</div>
Blocks are a tool type that interact with the editor's content through a modal form and a blade view. They can be used to insert custom content into the editor.
$editorView
is optional but can be useful in the case that you need to provide a custom editor view for the block. And a different rendering view for the output.
See the Pounce plugin docs for more information on the Alignment
, MaxWidth
, and SlideDirection
.
use Awcodes\Scribble\ScribbleTool;
use Awcodes\Pounce\Enums\MaxWidth;
use Awcodes\Pounce\Enums\Alignment;
use Awcodes\Pounce\Enums\SlideDirection;
use Awcodes\Scribble\Enums\ToolType;
class Notice extends ScribbleTool
{
protected function setUp(): void
{
$this
->icon('heroicon-o-exclamation-triangle')
->label('Notice')
->type(ToolType::Block)
->optionsModal(NoticeForm::class)
->renderedView('scribble-tools.notice');
}
}
use Awcodes\Scribble\Livewire\ScribbleModal;
use Awcodes\Scribble\Profiles\MinimalProfile;
use Awcodes\Scribble\ScribbleEditor;
use Filament\Forms\Components\Radio;
class NoticeForm extends ScribbleModal
{
public ?string $header = 'Notice';
// this should match the identifier in the tool class
public ?string $identifier = 'notice';
public function mount(): void
{
$this->form->fill([
'color' => $this->data['color'] ?? 'info',
'body' => $this->data['body'] ?? null,
]);
}
public function getFormFields(): array
{
return [
Radio::make('color')
->inline()
->inlineLabel(false)
->options([
'info' => 'Info',
'success' => 'Success',
'warning' => 'Warning',
'danger' => 'Danger',
]),
ScribbleEditor::make('body')
->profile(MinimalProfile::class)
->columnSpanFull(),
];
}
}
<div
@class([
'border-l-4 p-4 flex items-center gap-3 not-prose',
match($color) {
'success' => 'bg-success-200 text-success-900 border-success-600',
'danger' => 'bg-danger-200 text-danger-900 border-danger-600',
'warning' => 'bg-warning-200 text-warning-900 border-warning-600',
default => 'bg-info-200 text-info-900 border-info-600',
}
])
>
@php
$icon = match($color) {
'success' => 'heroicon-o-check-circle',
'danger' => 'heroicon-o-exclamation-circle',
'warning' => 'heroicon-o-exclamation-triangle',
default => 'heroicon-o-information-circle',
};
@endphp
@svg($icon, 'h-6 w-6')
{!! scribble($body)->toHtml() !!}
</div>
Modals are a tool type that interact with the editor's content through a modal form and use Tiptap commands to insert content into the editor. The Media
and Grid
tools are examples of this.
use Awcodes\Scribble\ScribbleTool;
use Awcodes\Scribble\Enums\ToolType;
use App\Path\To\MediaForm;
class Media extends ScribbleTool
{
protected function setUp(): void
{
$this
->icon('heroicon-o-photograph')
->label('Media')
->type(ToolType::Modal)
->commands([
$this->makeCommand(command: 'setMedia'),
])
->optionsModal(MediaForm::class);
}
}
use Awcodes\Scribble\Livewire\ScribbleModal;
use Awcodes\Scribble\Profiles\MinimalProfile;
use Awcodes\Scribble\ScribbleEditor;
use Filament\Forms\Components\Radio;
class MediaForm extends ScribbleModal
{
public ?string $header = 'Media';
// this should match the identifier in the tool class
public ?string $identifier = 'media';
public function mount(): void
{
$this->form->fill([
//
]);
}
public function getFormFields(): array
{
return [
//
];
}
}
You may also create tools that emit events when they are clicked. This can be useful for triggering actions in your application when a tool is clicked.
use Awcodes\Scribble\Enums\ToolType;
use Awcodes\Scribble\ScribbleTool;
class OpenRandomModal extends ScribbleTool
{
protected function setUp(): void
{
$this
->icon('scribble-open')
->label('Open Random Modal')
->type(ToolType::Event)
->commands([
$this->makeCommand(command: 'setDataFromEvent'),
])
->event(
name: 'open-modal',
data: [
'id' => 'random-modal',
'title' => 'Random Modal',
],
);
}
}
You can scaffold out a new tool class using the make:scribble-tool
command and following the prompts.
php artisan make:scribble-tool
You can also provide custom Tiptap extensions or other Tiptap native extensions to the editor. This can be useful for adding custom marks, nodes, or other extensions to the editor.
import {Highlight} from "@tiptap/extension-highlight";
import MyCustomExtension from "./MyCustomExtension";
window.scribbleExtensions = [
Highlight,
MyCustomExtension,
];
Next you will need to load your js file in your layout or view before Filament's scripts. This can be done in a way you see fit for you application.
For example, with a Filament Panel you could do something like the following:
public function panel(Panel $panel): Panel
{
return $panel
->renderHook(
name: 'panels::head.end',
hook: fn (): string => Blade::render('@vite("resources/js/scribble/extensions.js")')
);
}
In order for the content to be able to be converted to HTML, you will need to provide a PHP parser for the extension. See the Tiptap PHP package for more information on how to create a parser for a Tiptap extension or using an included one in their package.
Next you will need a make a tool for the extension.
use Awcodes\Scribble\ScribbleTool;
use Tiptap\Marks\Highlight as TiptapHighlight;
class Highlight extends ScribbleTool
{
protected function setUp(): void
{
$this
->icon('icon-highlight')
->label('Highlight')
->commands([
$this->makeCommand(command: 'toggleHighlight'),
])
->converterExtensions(new TiptapHighlight());
}
}
Now you can register the tool and PHP parser with the plugin in a ServiceProvider's register
method.
use Awcodes\Scribble\ScribbleManager;
use App\ScribbleTools\Highlight;
use Tiptap\Marks\Highlight as TiptapHighlight;
public function register(): void
{
app(ScribbleManager::class)
->registerTools([
Highlight::make(),
]);
}
use Awcodes\Scribble\Utils\Converter;
Converter::from($content)->toHtml();
Converter::from($content)->toJson();
Converter::from($content)->toText();
Converter::from($content)->toMarkdown();
Converter::from($content)->toTOC(); // Table of Contents
{!! scribble($content)->toHtml() !!}
{!! scribble($content)->toJson() !!}
{!! scribble($content)->toText() !!}
{!! scribble($content)->toMarkdown() !!}
{!! scribble($content)->toTOC() !!}
use Awcodes\Scribble\Utils\Converter;
// HTML output with headings linked and wrapped in anchor tags
Converter::from($content)
->toHtml(
toc: true,
maxDepth: 3,
wrapHeadings: true
);
// Structured list of heading links
Converter::from($content)->toTOC();
If you are using Merge tags and outputting the content as HTML you can use the mergeTagsMap
method to replace the merge tags with the appropriate values.
{!!
scribble($content)->mergeTagsMap([
'brand_phone' => '1-800-555-1234',
'brand_email' => '[email protected]',
])->toHtml()
!!}
use Awcodes\Scribble\Utils\Faker;
Faker::make()
->heading(int | string | null $level = 2)
->emptyParagraph()
->paragraphs(int $count = 1, bool $withRandomLinks = false)
->unorderedList(int $count = 1)
->orderedList(int $count = 1)
->image(?int $width = 640, ?int $height = 480)
->link()
->details(bool $open = false)
->code(?string $className = null)
->blockquote()
->hr()
->br()
->grid(array $cols = [1, 1, 1])
->toJson();
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.
git init git remote add origin https://github.com/monteduro/scribble.git
cd vendor/awcodes/scribble/ npm install node bin/build.js --dev