action-text
Action Text
Action Text adds rich-text content to a model. Content is stored as sanitized HTML against an allowlist, edited through a Trix editor, rendered with safe-buffer output, and can embed Active Storage attachments.
Rich-text content
A MVC::Keayl::ActionText::Content wraps a fragment of rich-text HTML.
Content.from-html sanitizes the input, keeping the rich-text allowlist of tags
and attributes and dropping everything else (scripts, event handlers, unknown
tags):
use MVC::Keayl::ActionText;
my $content = MVC::Keayl::ActionText::Content.from-html(
'<h1>Title</h1><script>evil()</script>'
);
$content.to-trix-html; # '<h1>Title</h1>' as a safe string, for editing
$content.to-html; # rendered html (attachments expanded), a safe string
$content.to-plain-text; # 'Title'
$content.is-empty; # Falsesanitize-rich-text($html) runs the same allowlist on its own.
Attaching rich text to a record
A model includes MVC::Keayl::ActionText::RichTextable and declares its
rich-text fields with has-rich-text:
use MVC::Keayl::ActionText::RichTextable;
class Article does RichTextable {
has $.id;
}
Article.has-rich-text('content');Each declared name returns a proxy backed by a rich-text record:
$article.content.assign('<h1>Hello</h1>'); # sanitizes and stores
$article.content.is-present; # True
$article.content.to-html; # the rendered html
$article.content.to-plain-text; # 'Hello'Records persist through a repository.
MVC::Keayl::ActionText::Repository defines the interface and
MemoryRepository is the in-process implementation. Configure an ORM-backed
repository at boot:
set-rich-text-repository(MemoryRepository.new);Editing
rich-text-area on the form builder emits the Trix editor markup: a hidden
input that carries the value plus a <trix-editor> bound to it.
use MVC::Keayl::Helpers::Form;
FormBuilder.new(object-name => 'post').rich-text-area('body');<input type="hidden" id="post_body_trix_input" name="post[body]" value="...">
<trix-editor input="post_body_trix_input" class="trix-content"></trix-editor>rich-text-area-tag(name, value?, %options) renders the same markup without a
form builder.
Rendering
The rich-text helper renders content for a view with safe-buffer output. It
accepts a Content, a rich-text proxy, or a raw HTML string (which it
sanitizes):
rich-text($article.content);
rich-text('<p>hello</p>');Embedded attachments
An Active Storage blob can be embedded in the document as an
<action-text-attachment> element carrying a signed blob id. embed-tag($blob)
builds one:
my $tag = embed-tag($blob);
# <action-text-attachment sgid="..." content-type="image/png" filename="photo.png"></action-text-attachment>When the content renders, each attachment resolves its signed id to a blob and
expands into a <figure>. An image becomes a preview pointing at the proxy
serving path; any other file becomes a download link. An attachment whose signed
id no longer resolves is dropped.
<figure class="attachment attachment--preview attachment--png">
<img src="/keayl/blobs/proxy/..." alt="photo.png">
</figure>content.attachment-sgids lists the signed ids embedded in a document.