hermanouchoa/rich-text-laravel

将 Trix 内容与 Laravel 集成

dev-main 2023-02-06 17:58 UTC

This package is auto-updated.

Last update: 2024-09-06 21:18:57 UTC


README

Logo Rich Text Laravel

Total Downloads License

集成了 Trix 编辑器 与 Laravel,灵感来自 Rails 的 Action Text 珍宝。

安装

您可以通过 composer 安装此包

composer require tonysm/rich-text-laravel

然后,您可以通过以下命令安装它

php artisan richtext:install

Blade 组件

如果您正在使用 Importmap Laravel 包,请确保您已将 Trix 核心样式 Blade 组件添加到布局文件(s)的 head 标签中

<x-rich-text-trix-styles />

如果您正在使用 Laravel Mix/Webpack,则 resources/js/trix.js 文件包含 JS 设置以及 CSS 导入,因此无需使用 Blade 组件。

此包还为您提供了一个 Blade 组件,您可以在表单中使用它,如下所示

<x-trix-field id="bio" name="bio" />

概述

我们在保存到数据库之前从富文本字段(使用 Trix)中提取附件,并最小化内容以进行存储。附件被替换为 rich-text-attachment 标签。可附加模型的附件具有 sgid 属性,该属性应在您的应用程序中全局识别它们。

当直接存储图像(例如,对于简单的图像上传,您在应用程序中没有表示该附件的模型)时,我们将使用所有必要的属性填充 rich-text-attachment 以重新呈现该图像。存储最小化(规范)版本的富文本内容意味着我们不会存储附件标签的内部内容,只存储在需要时重新呈现所需的相关元数据。

使用此包有两种方式

  1. 使用推荐的数据库结构,其中所有富文本内容都将存储在包含富文本内容的模型之外(推荐);
  2. 仅使用 AsRichTextContent 特性将富文本内容字段转换为任何模型,任何您想要的表。

以下我们将介绍每种使用方式。建议您至少在某些时候阅读 Trix 文档,以了解其客户端的概述。

RichText 模型

推荐的方式是保持富文本内容在模型本身之外。这将使您在操作时保持模型简洁,并且您可以在需要富文本内容的地方(积极或懒惰)加载富文本字段。

例如,在 Post 模型上会有两个富文本字段,例如您需要一个用于内容的正文,另一个用于可能有的内部注释

use Tonysm\RichTextLaravel\Models\Traits\HasRichText;

class Post extends Model
{
    use HasRichText;

    protected $guarded = [];

    protected $richTextFields = [
        'body',
        'notes',
    ];
}

此特性将在 Post 模型上创建 动态关系,每个字段一个。这些关系将被称为:richText{FieldName},您可以使用下划线定义字段,因此如果您有一个 internal_notes 字段,则将在模型上添加 richTextInternalNotes 关系。

为了更好的 DX,此特性还会在 Post 模型的 bodynotes 字段上添加自定义转换,以将设置/获取操作转发到关系,因为这些字段将不会存储在 posts 表中。这意味着您可以使用 Post 模型如下

$post = Post::create(['body' => $body, 'notes' => $notes]);

并且您可以像与 Post 模型上的任何常规字段一样与富文本字段交互

$post->body->render();

再次强调,在Post模型中没有bodynotes字段,这些虚拟字段将把交互转发到该字段的关联关系。这意味着当您与这些字段交互时,您实际上是在与RichText模型的一个实例进行交互。该模型将有一个包含丰富文本内容的body字段。然后,这个字段被转换为Content类的一个实例。对RichText模型的调用将被转发到RichText模型上的body字段,它是一个Content类的实例。这意味着,而不是

$post->body->body->attachments();

第一个"body"是虚拟字段,它将是RichText模型的一个实例,第二个"body"是这个模型上的丰富文本内容字段,它是一个Content类的实例,您可以这样做

$post->body->attachments();

与Content类类似,RichText模型将实现__toString魔法方法,并通过将其转换为字符串来渲染HTML内容(供最终用户使用),在blade中可以这样做

{!! $post->body !!}

注意:由于HTML输出不会被转义,在渲染之前请确保对其进行清理。您可以使用类似mews/purifier的包,有关更多信息请参阅清理部分。

HasRichText特性还将添加一个作用域,您可以使用它来预加载丰富文本字段(记住,每个字段都将有自己的关联关系),您可以使用如下方式

// Loads all rich text fields (1 query for each field, since each has its own relationship)
Post::withRichText()->get();

// Loads only a specific field:
Post::withRichText('body')->get();

// Loads some specific fields (but not all):
Post::withRichText(['body', 'notes'])->get();

此示例的数据库结构可能如下所示

posts
    id (primary key)
    created_at (timestamp)
    updated_at (timestamp)

rich_texts
    id (primary key)
    field (string)
    body (long text)
    record_type (string)
    record_id (unsigned big int)
    created_at (timestamp)
    updated_at (timestamp)

我们在rich_texts表中存储字段的反向引用,因为一个模型可能有多个丰富文本字段,所以它用于HasRichText为您创建的动态关系。此外,此表还有一个唯一约束,防止对同一个模型/字段对有多个条目。

将丰富文本内容渲染回Trix编辑器的方式与渲染给最终用户的方式略有不同,因此您可能需要使用字段的toTrixHtml方法来完成,如下所示

<x-trix-field id="post_body" name="body" value="{!! $post->body->toTrixHtml() !!}" />

接下来,转到附件部分,了解更多关于可附加对象的信息。

AsRichTextContent 特性

如果您不想使用推荐的结构(无论是由于您有强烈的意见还是您想控制自己的数据库结构),您可以选择跳过整个推荐的数据库结构,并在您的丰富文本内容字段上使用自定义的AsRichTextContent转换。例如,如果您在posts表上存储body字段,您可以这样做

use Tonysm\RichTextLaravel\Casts\AsRichTextContent;

class Post extends Model
{
    protected $casts = [
        'body' => AsRichTextContent::class,
    ];
}

然后,自定义转换将解析HTML内容并将其压缩以进行存储。本质上,它将转换Trix提交的内容,该内容仅包含一个图像附件

$post->update([
    'content' => <<<HTML
    <h1>Hello World</h1>
    <figure data-trix-attachment='{
        "url": "http://example.com/blue.jpg",
        "width": 300,
        "height": 150,
        "contentType": "image/jpeg",
        "caption": "Something cool",
        "filename":"blue.png",
        "filesize":1168
    }'>
        <img src="http://example.com/blue.jpg" width="300" height="150" />
        <caption>
            Something cool
        </caption>
    </figure>
    HTML,
])

为这个压缩版本

<h1>Hello World</h1>
<rich-text-attachment content-type="image/jpeg" filename="blue.png" filesize="1168" height="300" href="http://example.com/blue.jpg" url="http://example.com/blue.jpg" width="300" caption="testing this caption" presentation="gallery"></rich-text-attachment>

当再次渲染时,它将在rich-text-attachment标签内重新渲染远程图像。您可以通过简单地输出输出来渲染内容以进行查看,如下所示

{!! $post->content !!}

注意:由于HTML输出不会被转义,在渲染之前请确保对其进行清理。您可以使用类似mews/purifier的包,有关更多信息请参阅清理部分。

当再次向Trix编辑器馈送时,需要以不同的方式进行

<x-trix-field id="post_body" name="body" value="{!! $post->body->toTrixHtml() !!}" />

由于编辑器的渲染方式略有不同,因此必须这样。

图片上传

Trix默认的图像附件实现无法直接与Laravel配合使用。您需要自行实现图像上传,并在之后根据图像URL更新附件。以下是一个使用Stimulus的推荐实现方法,但您也可以选择任何您喜欢的前端框架。在这里,我们不会介绍如何在项目中设置Stimulus,您可以查阅他们的文档,或者如果您已经使用了Turbo Laravel包,您可以查看它是如何安装Stimulus的。

首先,我们需要创建一个Stimulus控制器,让我们称它为trix_controller.js

import { Controller } from "stimulus";

export default class extends Controller {
    // ...
}

然后,我们可以监听Trix编辑器在添加新的附件时发出的trix-attachment-add事件,如下所示

<x-trix-editor
    id="post_body"
    name="body"
    data-controller="trix"
    data-action="
        trix-attachment-add->trix#upload
    "
></x-trix-editor>

现在,让我们在刚刚创建的trix_controller.js中实现upload方法

import { Controller } from "stimulus";

export default class extends Controller {
    upload(event) {
        if (! event?.attachment?.file) {
            return;
        }

        this._uploadFile(event.attachment);
    }

    _uploadFile(attachment) {
        const form = new FormData();
        form.append('attachment', attachment.file);

        window.axios.post('/attachments', form, {
            onUploadProgress: (progressEvent) => {
                attachment.setUploadProgress(progressEvent.loaded / progressEvent.total * 100);
            }
        }).then(resp => {
            attachment.setAttributes({
                url: resp.data.image_url,
                href: resp.data.image_url,
            });
        });
    }
}

这将向/attachments发送POST请求,其中包含attachment字段,它应该是一个文件Blob。期望的响应应包含一个image_url字段。以下是Laravel中该路由可能的样子

Route::middleware(['auth:sanctum', 'verified'])->post('attachments', function () {
    request()->validate([
        'attachment' => ['required', 'file'],
    ]);

    $path = request()->file('attachment')->store('trix-attachments', 'uploads');

    return [
        'image_url' => Storage::disk('uploads')->url($path),
    ];
})->name('attachments.store');

在这个例子中,图像将存储在trix-attachments/文件夹中的uploads磁盘上,并且该文件的URL将作为image_url属性返回。这个图像将以远程图像的形式存储在Trix内容中。这只是一个图像上传的简化版本。另一种方法是使用Spatie的Media Library包,并自定义Media模型,使其也成为可附加的。这样,Media模型将有自己的SGID,并且您还需要在附件中将该属性设置,如下所示

attachment.setAttributes({
    sgid: resp.data.sgid,
    url: resp.data.image_url,
    href: resp.data.image_url,
});

这将允许更高级的操作,例如在富文本内容中检索所有Media模型的附件,并将嵌入的Media附件作为具有富文本内容的模型的关联关系保存,就像我们在附件部分所做的那样。这样,您就有了在富文本代码中使用的图像的引用(如果您想以后修剪图像,这可能很有用)。

内容附件

使用Trix,我们可以有内容附件。为了涵盖这一点,让我们在Trix上构建一个用户提及功能。有一个很好的Rails Conf演讲,介绍了这个整个功能的构建,但使用的是Rails。JavaScript部分是相同的,所以我们在这里重新创建那个部分。

要将用户模型转换为Attachable,您必须实现AttachableContract并使用Attachable特性在用户模型上。除此之外,您还必须实现一个richTextRender(array $options): string,其中您告诉包如何在该模型内部渲染Trix

use Tonysm\RichTextLaravel\Attachables\AttachableContract;
use Tonysm\RichTextLaravel\Attachables\Attachable;

class User extends Model implements AttachableContract
{
    use Attachable;

    public function richTextRender(array $options = []): string
    {
        return view('users._mention', [
            'user' => $this,
        ])->render();
    }
}

传递给richTextRender$options数组用于在相册中渲染多个模型的情况,因此在这种情况下,您将获得一个包含in_gallery布尔字段(可选)的数组,但在本用户提及示例中并非如此,因此我们可以忽略它。

然后,在users._mention Blade模板中,您可以完全控制此可附加字段的HTML。您可能希望在附件中显示用户的头像和他们的名字,在span标签内,所以users._mention视图将如下所示

<span class="flex items-center space-x-1">
    <img src="{{ $user->profile_photo_url }}" alt="{{ $user->name }}" class="inline-block object-cover w-4 h-4 rounded-full" />
    <span>{{ $user->name }}</span>
</span>

现在,让我们转向下拉菜单,并设置每当用户在Trix编辑器中输入@符号时,下拉菜单自动打开。您可以使用类似Zurb的Tribute的工具,或者您可以构建自己的下拉菜单,并通过监听编辑器中的keydown事件来检测用户输入@符号以打开下拉菜单。您可以选择。让我们首先创建一个新的Stimulus控制器,名为mentions_controller.js

import { Controller } from "stimulus";

export default class extends Controller {
    // ...
}

接下来,我们将导入Tribute,并在控制器连接到其附加的DOM元素时启动它。同时,在控制器断开连接时将其分离,以及它将使用哪个方法来查找用户(fetchUsers)。我们需要将Tribute附加到元素上,以便它知道在哪里添加触发提及下拉菜单的事件监听器。我们还需要覆盖Tribute在选项被选中时所做的操作,这就是为什么我们在实例中添加我们自己的range.pasteHtml方法实现(请参见下面的代码)。我们还需要

import { Controller } from "stimulus";
import Tribute from 'tributejs';
import Trix from 'trix';

require('tributejs/tribute.css');

export default class extends Controller {
    connect() {
        this.initializeTribute();
    }

    disconnect() {
        this.tribute.detach(this.element);
    }

    initializeTribute() {
        this.tribute = new Tribute({
            allowSpaces: true,
            lookup: 'name',
            values: this.fetchUsers,
        })

        this.tribute.attach(this.element);
        this.tribute.range.pasteHtml = this._pasteHtml.bind(this);
    }

    fetchUsers(text, callback) {
        window.axios.get(`/mentions?search=${text}`)
            .then(resp => callback(resp.data))
            .catch(error => callback([]))
    }

    _pasteHtml(html, startPosition, endPosition) {
        // We need to remove everything the user has typed
        // while searching for a user. We'll later inject
        // the mention into Trix as a content attachment.

        let range = this.editor.getSelectedRange();
        let position = range[0];
        let length = endPosition - startPosition;

        this.editor.setSelectedRange([position - length, position])
        this.editor.deleteInDirection('backward');
    }
}

现在,我们需要将提及控制器附加到Trix编辑器上,就像我们在图片上传示例中所做的那样

<x-trix-editor
    id="post_body"
    name="body"
    data-controller="trix mentions"
    data-action="
        trix-attachment-add->trix#upload
    "
></x-trix-editor>

GET /mentions?search=路由可能看起来像这样

Route::middleware(['auth:sanctum', 'verified'])->get('mentions', function () {
    return auth()->user()->currentTeam->allUsers()
        ->when(request('search'), fn ($users, $search) => (
            $users->filter(fn (User $user) => (
                str_starts_with(strtolower($user->name), strtolower($search))
            ))
        ))
        ->sortBy('name')
        ->take(10)
        ->map(fn (User $user) => [
            'sgid' => $user->richTextSgid(),
            'name' => $user->name,
            'content' => $user->richTextRender(),
        ])
        ->values();
})->name('mentions.index');

您可以看到我们返回了sgid,这是从Attachable特质中的一种方法。它基本上为您的应用程序中的此模型生成一个唯一的全局标识符。更多关于这一点的内容,请参阅SGDI部分。它还返回用户的姓名,这将由Tribute用于显示选项,以及内容,这是我们将插入到Trix.Attachment中的富文本渲染。但是,如果您尝试运行此代码,则不应如您预期那样工作。在选择选项后,您在查找选项时输入的内容(例如@Ton),应消失,但没有附加任何内容。这是因为我们尚未实现这部分。

当您在Tribute中选择一个选项时,您需要监听tribute-replaced事件并调用mentions_controller.js中的方法,让我们挂钩它

<x-trix-editor
    id="post_body"
    name="body"
    data-controller="trix mentions"
    data-action="
        trix-attachment-add->trix#upload
        tribute-replaced->mentions#tributeReplaced
    "
></x-trix-editor>

接下来,让我们在我们的提及控制器中实现该方法。我们获得的那个事件应包含用户对象(包含我们从控制器返回的具有sgidnamecontent属性的detail.item.original路径中的对象)。我们可以使用它创建一个Trix.Attachment的实例,并将sgidcontent属性传递给它,然后将该附件插入到编辑器中

import { Controller } from "stimulus";
import Tribute from 'tributejs';
import Trix from 'trix';

require('tributejs/tribute.css');

export default class extends Controller {
    // ...

    tributeReplaced(e) {
        let mention = e.detail.item.original;
        let attachment = new Trix.Attachment({
            sgid: mention.sgid,
            content: mention.content,
        });

        this.editor.insertAttachment(attachment);
        this.editor.insertString(" ");
    }

    get editor() {
        return this.element.editor;
    }
}

现在我们完成了。这里的示例是使用用户提及,但您实际上可以将任何内容附加到Trix文档中。您完全控制该文档的渲染方式。当该文档提交到您的后端以存储时,该包将最小化任何内容附件,类似于在图片上传示例中所做的那样。但这一次,将使用sgid来识别被提及的用户附件,并且将在稍后再次显示该文档时渲染users._mention Blade模板。这很有用,因为您可以在不担心数据库中的静态文档的情况下调整应用程序中提及的外观。

您可以在以后从丰富的文本内容中检索所有附件。有关更多信息,请参阅内容对象部分。

内容对象

您可能希望在以后某个时间点检索该富文本内容中的所有附件,并对其进行一些特殊处理,例如实际存储与Post模型关联的用户提及,例如。或者,您可以检索该富文本内容中的所有链接并对其进行处理。

获取附件

您可以使用attachments()方法从富内容字段中检索所有附件,无论是在RichText模型实例还是在Content实例中

$post->body->attachments()

这将返回所有附件的集合,实际上是所有可附件的集合,例如图像和用户 - 如果您只想获取特定可附件的附件,您可以使用集合上的过滤方法,如下所示

// Getting only attachments of users inside the rich text content.
$post->body->attachments()
    ->filter(fn (Attachment $attachment) => $attachment->attachable instanceof User)
    ->map(fn (Attachment $attachment) => $attachment->attachable)
    ->unique();

获取链接

要从富文本内容中提取链接,您可以调用links()方法,如下所示

$post->body->links()

获取附件画廊

Trix有一个画廊的概念,您可能想要检索所有画廊

$post->body->attachmentGalleries()

这将返回所有图像画廊的DOMElement集合。

获取画廊附件

您可能还只想获取图像画廊中的附件。您可以通过以下方式实现这一点

$post->body->galleryAttachments()

这将返回一个包含画廊中所有图像附件的集合(全部)。然后您可以通过以下方式检索RemoteImage可附件实例

$post->body->galleryAttachments()
    ->map(fn (Attachment $attachment) => $attachment->attachable)

纯文本渲染

Trix内容可以转换为任何东西。这本质上意味着HTML > something。该包附带了一个HTML > Plain Text实现,因此您可以通过对它调用toPlainText()方法将其转换为纯文本

$post->body->toPlainText()

例如,以下富文本内容

<h1>Very Important Message<h1>
<p>This is an important message, with the following items:</p>
<ol>
    <li>first item</li>
    <li>second item</li>
</ol>
<p>And here's an image:</p>
<rich-text-attachment content-type="image/jpeg" filename="blue.png" filesize="1168" height="300" href="http://example.com/blue.jpg" url="http://example.com/blue.jpg" width="300" caption="The caption of the image" presentation="gallery"></rich-text-attachment>
<br><br>
<p>With a famous quote</p>
<blockquote>Lorem Ipsum Dolor - Lorense Ipsus</blockquote>
<p>Cheers,</p>

将被转换为

Very Important Message

This is an important message, with the following items:

    1. first item
    1. second item

And here's an image:

[The caption of the image]

With a famous quote

“Lorem Ipsum Dolor - Lorense Ipsus”

Cheers,

如果您正在附加模型,您可以在其上实现richTextAsPlainText(?string $caption = null): string方法,其中您应该返回该可附件的纯文本表示。如果该方法未在可附件上实现,并且Trix附件中未存储标题,则该附件将不会出现在内容的纯文本版本中。

净化

由于我们输出未转义的HTML,您需要净化以避免任何安全问题。一个建议是在最终渲染之前(除了渲染在为Trix提供输入的字段值属性之外)使用mews/purifier包。这看起来像这样

{!! clean($post->body) !!}

您需要添加一些自定义设置到安装mews/purifier包时创建的配置文件,如下所示

return [
    // ...
    'settings' => [
        // ...
        'default' => [
            // ...
            'HTML.Allowed' => 'rich-text-attachment[sgid|content-type|url|href|filename|filesize|height|width|previewable|presentation|caption|data-trix-attachment|data-trix-attributes],div,b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[width|height|alt|src],del,h1,blockquote,figure[data-trix-attributes|data-trix-attachment],figcaption,*[class]',
        ],
        // ...
        'custom_definition' => [
            // ...
            'elements' => [
                // ...
                ['rich-text-attachment', 'Block', 'Flow', 'Common'],
            ],
        ],
        // ...
        'custom_attributes' => [
            // ...
            ['rich-text-attachment', 'sgid', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'content-type', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'url', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'href', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'filename', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'filesize', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'height', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'width', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'previewable', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'presentation', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'caption', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'data-trix-attachment', new HTMLPurifier_AttrDef_Text],
            ['rich-text-attachment', 'data-trix-attributes', new HTMLPurifier_AttrDef_Text],
            ['figure', 'data-trix-attachment', new HTMLPurifier_AttrDef_Text],
            ['figure', 'data-trix-attributes', new HTMLPurifier_AttrDef_Text],
        ],
        // ...
        'custom_elements' => [
            // ...
            ['rich-text-attachment', 'Block', 'Flow', 'Common'],
        ],
    ],
];

注意:我不是HTML内容净化的专家,所以请多加小心,并在可能的情况下咨询在这方面经验更丰富的人。

电子邮件渲染

如果您想通过电子邮件发送Trix内容,它可以在Mailable中渲染并交付给用户。Laravel的默认电子邮件主题清晰地呈现Trix内容,即使您正在使用Markdown消息。

为了确保您的内容在不同电子邮件客户端中都能良好显示,您应该始终使用mews/purifier包净化您的渲染HTML(如上所述),但使用自定义规则集删除可能会影响消息布局的标签。

mews/purifier配置中添加一个新的mail规则(注意有关净化和安全性的早期评论)

return [
    // ...
    'settings' => [
        // ...
        'mail'              => [
            'HTML.Allowed' => 'div,b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[alt|src],del,h1,h2,sup,blockquote,figure,figcaption,*[class]',
            'CSS.AllowedProperties'    => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align',
            'AutoFormat.AutoParagraph' => true,
            'AutoFormat.RemoveEmpty'   => true,
        ],
        // ...

此规则与正常配置的不同之处在于从<img>元素中删除widthheight标签,并将<pre><code>标签转换为普通段落(因为这些似乎会触发Markdown解析器)。如果您依赖Trix中的代码块,您可能能够调整净化规则集来解决这个问题。

要通过电子邮件发送富文本内容,创建消息的Blade模板,如下面的示例所示

@component('mail::message')

# Hi {{ $user->name }},

## We've just published a new article: {{ $article->title }}

<!-- //@formatter:off -->
{!! preg_replace('/^ +/m', '', clean($article->content->render(), 'mail')) !!}
<!-- //@formatter:on -->

@endcomponent

虽然Blade消息使用Markdown进行问候,但Trix内容将以HTML的形式渲染。这只会在没有任何缩进的情况下正确渲染 - 否则,Markdown解析器会尝试将嵌套内容解释为代码块。

使用 preg_replace() 函数从清理后的 Trix 内容的每一行中删除前导空格。在 clean() 函数中,第二个参数指定它引用上述描述的 mail 配置条目。

//@formatter:* 注释是可选的,但如果您使用 PhpStorm 这样的 IDE,这些注释可以防止它在运行代码清理工具时尝试自动缩进元素。

SGID

在存储自定义附件的引用时,该包使用另一个名为 GlobalID Laravel 的包。我们存储一个签名全局 ID,这意味着用户不能简单地更改静态的 sgids。他们需要使用秘密的 APP_KEY 生成另一个有效的签名。

如果您想更换密钥,则需要遍历所有富文本内容,找到所有具有 sgid 属性的可附件,使用新的秘密为新签名分配一个新值,并使用这个新值存储内容。

Livewire

如果您将具有富文本字段的模型绑定到 Livewire 组件,您可以在组件中添加 WithRichTexts 特性。此外,建议您将富文本保持为原始形式,直到您想要将其保存到富文本字段的那一刻,如下所示

<?php

namespace App\Http\Livewire;

use App\Models\User;
use Livewire\Component;
use Tonysm\RichTextLaravel\Livewire\WithRichTexts;

class UserProfileForm extends Component
{
    use WithRichTexts;

    public User $user;
    public $bio = '';

    protected $rules = [
        'bio' => ['required'],
    ];

    public function mount(User $user)
    {
        $this->user = $user;
        $this->bio = $user->bio->toTrixHtml();
    }

    public function save()
    {
        $this->validate();

        $this->user->update([
            'bio' => $this->bio,
        ]);
    }
}

在这个示例中,用户模型有一个 bio 富文本字段。

请参阅示例中用户模型的全部内容
<?php

use Illuminate\Foundation\Auth\User as Authenticatable;
use Tonysm\RichTextLaravel\Models\Traits\HasRichText;

class User extends Model
{
    use HasRichText;

    protected $fillable = ['bio'];
    protected $richTextFields = ['bio'];
}

测试

composer test

更新日志

请参阅 更新日志 了解最近更改的详细信息。

贡献

请参阅 贡献指南 了解详细信息。

安全漏洞

请参阅我们关于如何报告安全漏洞的 安全策略

致谢

许可协议

MIT 许可协议 (MIT)。请参阅 许可文件 了解更多信息。