awcodes/scribble

为 Filament Forms 设计的富文本编辑器插件。

支持包维护!
awcodes


README

Latest Version on Packagist Total Downloads

table repeater opengraph image

为 Filament Forms 设计的富文本编辑器插件。

安装

通过 composer 安装包

composer require awcodes/scribble

发布配置

php artisan vendor:publish --tag="scribble-config"

发布翻译

php artisan vendor:publish --tag="scribble-translations"

设置插件的样式

重要

如果您尚未设置自定义主题并且正在使用 Filament Panels,请先按照Filament 文档中的说明进行操作。以下内容适用于面板包和独立表单包。

将插件的样式表(如果尚未包含)导入到您的主题的 CSS 文件中。

@import '/vendor/awcodes/scribble/resources/css/editor.css';
@import '/vendor/awcodes/scribble/resources/css/entry.css';

将插件的视图添加到您的 tailwind.config.js 文件中。

content: [
    './vendor/awcodes/scribble/resources/**/*{.blade.php,.svelte}',
]

重新构建您的自定义主题。

npm run build

准备您的模型

Scribble 将其内容作为 JSON 数据存储在您的模型的单个列中。因此,在您的模型中将该列转换为数组或 JSON 对象至关重要。

protected $casts = [
    'content' => 'array', // or 'json'
];

还建议在迁移中将该列设置为 longText 类型。但这不是必需的,如果您知道您不需要大量数据,也可以使用 textmediumText 类型。只需注意内容可能会迅速增长。

$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')
        ]);
}

全局配置

在 ServiceProvider 的 boot 方法中,您可以使用 configureUsing 方法设置所有编辑器实例的默认配置。

use Awcodes\Scribble\ScribbleEditor;
use Awcodes\Scribble\Profiles\MinimalProfile;

ScribbleEditor::configureUsing(function (ScribbleEditor $scribble) {
    $scribble
        ->renderToolbar()
        ->profile(MinimalProfile::class);
});

编辑器配置文件

手动为每个编辑器实例创建菜单配置可能很繁琐。为了减轻这一点,您可以创建一个配置文件类,该类定义了气泡、建议和工具栏菜单的工具。然后,您可以使用 profile 方法将配置文件应用到编辑器上。您可以使用工具标识符或工具的类名。

namespace App\ScribbleProfiles;

use Awcodes\Scribble\ScribbleProfile;use Awcodes\Scribble\Tools;

class Minimal extends ScribbleProfile
{
    public static function bubbleTools(): array
    {
        return [
            Tools\Paragraph::class,
            Tools\Bold::class,
            Tools\Italic::class,
            Tools\Link::class,
            Tools\BulletList::class,
            Tools\OrderedList::class,
        ];
    }

    public static function suggestionTools(): array
    {
        return [];
    }

    public static function toolbarTools(): array
    {
        return [
            'paragraph',
            'bold',
            'italic',
            'link',
            'bullet-list',
            'ordered-list',
        ];
    }
}
use App\ScribbleProfiles\Minimal;

Scribble::configureUsing('content')
    ->profile(Mimimal::class)

生成配置文件

您可以使用 make:scribble-profile 命令并根据提示生成新的配置文件类。

php artisan make:scribble-profile

自定义编辑器样式

如果您需要为自定义块或工具提供样式,您可以使用 customStyles 方法提供一个 CSS 文件的路径。

Scribble::make('content')
    ->customStyles('path/to/custom.css')

扩展编辑器

自定义工具

命令

命令工具用于使用 Tiptap 命令将内容插入编辑器。例如,粗体斜体 工具。这也是默认的工具类型。

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],
            ]);
    }
}

静态块

静态块是一种工具类型,可以用于将静态 blade 视图插入编辑器。这对于插入可以渲染到 HTML 中不同视图的占位符内容很有用。例如,表示 FAQ 列表的块,在前端渲染时将显示数据库中的 FAQ 列表。

$editorView 是可选的,但在您需要为块提供自定义编辑器视图并为其输出提供不同渲染视图的情况下可能很有用。

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>

块是一种工具类型,通过模态表单和 blade 视图与编辑器的内容交互。它们可以用于将自定义内容插入编辑器。

$editorView 是可选的,但在您需要为块提供自定义编辑器视图并为其输出提供不同渲染视图的情况下可能很有用。

工具类
use Awcodes\Scribble\ScribbleTool;
use Awcodes\Scribble\Enums\Alignment;
use Awcodes\Scribble\Enums\SlideDirection;
use Awcodes\Scribble\Enums\ToolType;
use Filament\Support\Enums\MaxWidth;

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(),
        ];
    }
}
blade 视图
<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>

模态框

模态框是一种工具类型,通过模态表单与编辑器的内容交互,并使用 Tiptap 命令将内容插入编辑器。例如,媒体网格 工具。

工具类
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 [
            //
        ];
    }
}

事件

您还可以创建在点击时发出事件的工具。这可以在工具被点击时触发应用程序中的操作很有用。

工具类
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',
                ],
            );
    }
}

生成工具

您可以使用 make:scribble-tool 命令并遵循提示来创建一个新的工具类。

php artisan make:scribble-tool

自定义 Tiptap 扩展

您还可以向编辑器提供自定义 Tiptap 扩展或其他 Tiptap 原生扩展。这可以用于向编辑器添加自定义标记、节点或其他扩展。

JavaScript

import {Highlight} from "@tiptap/extension-highlight";
import MyCustomExtension from "./MyCustomExtension";

window.scribbleExtensions = [
    Highlight,
    MyCustomExtension,
];

接下来,您需要在 Filament 的脚本之前在布局或视图中加载您的 js 文件。您可以根据应用程序的需要进行操作。

例如,使用 Filament 面板,您可以执行以下操作

public function panel(Panel $panel): Panel
{
    return $panel
        ->renderHook(
            name: 'panels::head.end',
            hook: fn (): string => Blade::render('@vite("resources/js/scribble/extensions.js")')
        );
}

PHP 解析器

为了将内容转换为 HTML,您需要为扩展提供 PHP 解析器。有关如何为 Tiptap 扩展创建解析器或使用其包中包含的一个的信息,请参阅 Tiptap PHP 包。

工具

接下来,您需要为扩展制作一个工具。

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());
    }
}

现在,您可以在 ServiceProvider 的 register 方法中将工具和 PHP 解析器与插件注册。

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();

合并标签替换

如果您使用合并标签并将内容输出为 HTML,则可以使用 mergeTagsMap 方法用适当的值替换合并标签。

{!!
    scribble($content)->mergeTagsMap([
        'brand_phone' => '1-800-555-1234',
        'brand_email' => 'test@example.com',
    ])->toHtml()
!!}

Faker 实用程序

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

变更日志

有关最近更改的更多信息,请参阅 变更日志

贡献

有关详细信息,请参阅 贡献指南

安全漏洞

请查看 我们的安全策略 了解如何报告安全漏洞。

致谢

许可协议

MIT 许可协议 (MIT)。有关更多信息,请参阅 许可文件