forked from rails/rails
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
144 changed files
with
9,447 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/test/dummy/db/*.sqlite3 | ||
/test/dummy/db/*.sqlite3-journal | ||
/test/dummy/log/*.log | ||
/test/dummy/tmp/ | ||
/tmp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
* Added to Rails. | ||
|
||
*DHH* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2019 Basecamp, LLC | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# Action Text | ||
|
||
Action Text brings rich text content and editing to Rails. It includes the [Trix editor](https://trix-editor.org/) that handles everything from formatting to links to quotes to lists to embedded images and galleries. The rich text content generated by the Trix editor is saved in its own RichText model that's associated with any existing Active Record model in the application. Any embedded images (or other attachments) are automatically stored using Active Storage and associated with the included RichText model. | ||
|
||
## Trix compared to other rich text editors | ||
|
||
Most WYSIWYG editors are wrappers around HTML’s `contenteditable` and `execCommand` APIs, designed by Microsoft to support live editing of web pages in Internet Explorer 5.5, and [eventually reverse-engineered](https://blog.whatwg.org/the-road-to-html-5-contenteditable#history) and copied by other browsers. | ||
|
||
Because these APIs were never fully specified or documented, and because WYSIWYG HTML editors are enormous in scope, each browser’s implementation has its own set of bugs and quirks, and JavaScript developers are left to resolve the inconsistencies. | ||
|
||
Trix sidesteps these inconsistencies by treating contenteditable as an I/O device: when input makes its way to the editor, Trix converts that input into an editing operation on its internal document model, then re-renders that document back into the editor. This gives Trix complete control over what happens after every keystroke, and avoids the need to use execCommand at all. | ||
|
||
## Installation | ||
|
||
Run `rails action_text:install` to add the Yarn package and copy over the necessary migration. | ||
|
||
## Examples | ||
|
||
Adding a rich text field to an existing model: | ||
|
||
```ruby | ||
# app/models/message.rb | ||
class Message < ApplicationRecord | ||
has_rich_text :content | ||
end | ||
``` | ||
|
||
Then refer to this field in the form for the model: | ||
|
||
```erb | ||
<%# app/views/messages/_form.html.erb %> | ||
<%= form_with(model: message) do |form| %> | ||
… | ||
<div class="field"> | ||
<%= form.label :content %> | ||
<%= form.rich_text_area :content %> | ||
</div> | ||
… | ||
<% end %> | ||
``` | ||
|
||
And finally display the sanitized rich text on a page: | ||
|
||
```erb | ||
<%= @message.content %> | ||
``` | ||
|
||
To accept the rich text content, all you have to do is permit the referenced attribute: | ||
|
||
```ruby | ||
class MessagesController < ApplicationController | ||
def create | ||
message = Message.create! params.require(:message).permit(:title, :content) | ||
redirect_to message | ||
end | ||
end | ||
``` | ||
|
||
## Custom styling | ||
|
||
By default, the Action Text editor and content is styled by the Trix defaults. If you want to change these defaults, you'll want to remove the `app/assets/stylesheets/actiontext.css` linker and base your stylings on the [contents of that file](https://raw.githubusercontent.com/basecamp/trix/master/dist/trix.css). | ||
|
||
You can also style the HTML used for embedded images and other attachments (known as blobs). On installation, Action Text will copy over a partial to `app/views/active_storage/blobs/_blob.html.erb`, which you can specialize. | ||
|
||
## License | ||
|
||
Action Text is released under the [MIT License](https://opensource.org/licenses/MIT). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# frozen_string_literal: true | ||
|
||
require "bundler/setup" | ||
require "bundler/gem_tasks" | ||
require "rake/testtask" | ||
|
||
Rake::TestTask.new do |t| | ||
t.libs << "test" | ||
t.pattern = "test/**/*_test.rb" | ||
t.verbose = true | ||
end | ||
|
||
task default: :test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# frozen_string_literal: true | ||
|
||
version = File.read(File.expand_path("../RAILS_VERSION", __dir__)).strip | ||
|
||
# Describe your gem and declare its dependencies: | ||
Gem::Specification.new do |s| | ||
s.platform = Gem::Platform::RUBY | ||
s.name = "actiontext" | ||
s.version = version | ||
s.summary = "Rich text framework." | ||
s.description = "Edit and display rich text in Rails applications." | ||
|
||
s.required_ruby_version = ">= 2.5.0" | ||
|
||
s.license = "MIT" | ||
|
||
s.authors = ["Javan Makhmali", "Sam Stephenson", "David Heinemeier Hansson"] | ||
s.email = ["[email protected]", "[email protected]", "[email protected]"] | ||
s.homepage = "https://rubyonrails.org" | ||
|
||
s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*", "app/**/*", "config/**/*", "db/**/*"] | ||
s.require_path = "lib" | ||
|
||
s.metadata = { | ||
"source_code_uri" => "https://github.com/rails/rails/tree/v#{version}/actiontext", | ||
"changelog_uri" => "https://github.com/rails/rails/blob/v#{version}/actiontext/CHANGELOG.md" | ||
} | ||
|
||
# NOTE: Please read our dependency guidelines before updating versions: | ||
# https://edgeguides.rubyonrails.org/security.html#dependency-management-and-cves | ||
|
||
s.add_dependency "activesupport", version | ||
s.add_dependency "activerecord", version | ||
s.add_dependency "activestorage", version | ||
s.add_dependency "actionpack", version | ||
|
||
s.add_dependency "nokogiri", ">= 1.8.5" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# frozen_string_literal: true | ||
|
||
module ActionText | ||
module ContentHelper | ||
SANITIZER = Rails::Html::Sanitizer.white_list_sanitizer | ||
ALLOWED_TAGS = SANITIZER.allowed_tags + [ ActionText::Attachment::TAG_NAME, "figure", "figcaption" ] | ||
ALLOWED_ATTRIBUTES = SANITIZER.allowed_attributes + ActionText::Attachment::ATTRIBUTES | ||
|
||
def render_action_text_content(content) | ||
content = content.render_attachments do |attachment| | ||
unless attachment.in?(content.gallery_attachments) | ||
attachment.node.tap do |node| | ||
node.inner_html = render(attachment, in_gallery: false).chomp | ||
end | ||
end | ||
end | ||
|
||
content = content.render_attachment_galleries do |attachment_gallery| | ||
render(layout: attachment_gallery, object: attachment_gallery) do | ||
attachment_gallery.attachments.map do |attachment| | ||
attachment.node.inner_html = render(attachment, in_gallery: true).chomp | ||
attachment.to_html | ||
end.join("").html_safe | ||
end.chomp | ||
end | ||
|
||
sanitize content.to_html, tags: ALLOWED_TAGS, attributes: ALLOWED_ATTRIBUTES | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# frozen_string_literal: true | ||
|
||
module ActionText | ||
module TagHelper | ||
cattr_accessor(:id, instance_accessor: false) { 0 } | ||
|
||
# Returns a `trix-editor` tag that instantiates the Trix JavaScript editor as well as a hidden field | ||
# that Trix will write to on changes, so the content will be sent on form submissions. | ||
# | ||
# ==== Options | ||
# * <tt>:class</tt> - Defaults to "trix-content" which ensures default styling is applied. | ||
# | ||
# ==== Example | ||
# | ||
# rich_text_area_tag "content", message.content | ||
# # <input type="hidden" name="content" id="trix_input_post_1"> | ||
# # <trix-editor id="content" input="trix_input_post_1" class="trix-content" ...></trix-editor> | ||
def rich_text_area_tag(name, value = nil, options = {}) | ||
options = options.symbolize_keys | ||
|
||
options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}" | ||
options[:class] ||= "trix-content" | ||
|
||
options[:data] ||= {} | ||
options[:data][:direct_upload_url] = main_app.rails_direct_uploads_url | ||
options[:data][:blob_url_template] = main_app.rails_service_blob_url(":signed_id", ":filename") | ||
|
||
editor_tag = content_tag("trix-editor", "", options) | ||
input_tag = hidden_field_tag(name, value, id: options[:input]) | ||
|
||
input_tag + editor_tag | ||
end | ||
end | ||
end | ||
|
||
module ActionView::Helpers | ||
class Tags::ActionText < Tags::Base | ||
delegate :dom_id, to: ActionView::RecordIdentifier | ||
|
||
def render | ||
options = @options.stringify_keys | ||
add_default_name_and_id(options) | ||
options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_")) if object | ||
@template_object.rich_text_area_tag(options.delete("name"), editable_value, options) | ||
end | ||
|
||
def editable_value | ||
value&.body.try(:to_trix_html) | ||
end | ||
end | ||
|
||
module FormHelper | ||
# Returns a `trix-editor` tag that instantiates the Trix JavaScript editor as well as a hidden field | ||
# that Trix will write to on changes, so the content will be sent on form submissions. | ||
# | ||
# ==== Options | ||
# * <tt>:class</tt> - Defaults to "trix-content" which ensures default styling is applied. | ||
# | ||
# ==== Example | ||
# form_with(model: @message) do |form| | ||
# form.rich_text_area :content | ||
# end | ||
# # <input type="hidden" name="message[content]" id="message_content_trix_input_message_1"> | ||
# # <trix-editor id="content" input="message_content_trix_input_message_1" class="trix-content" ...></trix-editor> | ||
def rich_text_area(object_name, method, options = {}) | ||
Tags::ActionText.new(object_name, method, self, options).render | ||
end | ||
end | ||
|
||
class FormBuilder | ||
def rich_text_area(method, options = {}) | ||
@template.rich_text_area(@object_name, method, objectify_options(options)) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { DirectUpload } from "activestorage" | ||
|
||
export class AttachmentUpload { | ||
constructor(attachment, element) { | ||
this.attachment = attachment | ||
this.element = element | ||
this.directUpload = new DirectUpload(attachment.file, this.directUploadUrl, this) | ||
} | ||
|
||
start() { | ||
this.directUpload.create(this.directUploadDidComplete.bind(this)) | ||
} | ||
|
||
directUploadWillStoreFileWithXHR(xhr) { | ||
xhr.upload.addEventListener("progress", event => { | ||
const progress = event.loaded / event.total * 100 | ||
this.attachment.setUploadProgress(progress) | ||
}) | ||
} | ||
|
||
directUploadDidComplete(error, attributes) { | ||
if (error) { | ||
throw new Error(`Direct upload failed: ${error}`) | ||
} | ||
|
||
this.attachment.setAttributes({ | ||
sgid: attributes.attachable_sgid, | ||
url: this.createBlobUrl(attributes.signed_id, attributes.filename) | ||
}) | ||
} | ||
|
||
createBlobUrl(signedId, filename) { | ||
return this.blobUrlTemplate | ||
.replace(":signed_id", signedId) | ||
.replace(":filename", encodeURIComponent(filename)) | ||
} | ||
|
||
get directUploadUrl() { | ||
return this.element.dataset.directUploadUrl | ||
} | ||
|
||
get blobUrlTemplate() { | ||
return this.element.dataset.blobUrlTemplate | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import * as Trix from "trix" | ||
import { AttachmentUpload } from "./attachment_upload" | ||
|
||
addEventListener("trix-attachment-add", event => { | ||
const { attachment, target } = event | ||
|
||
if (attachment.file) { | ||
const upload = new AttachmentUpload(attachment, target) | ||
upload.start() | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
# The RichText record holds the content produced by the Trix editor in a serialized `body` attribute. | ||
# It also holds all the references to the embedded files, which are stored using Active Storage. | ||
# This record is then associated with the Active Record model the application desires to have | ||
# rich text content using the `has_rich_text` class method. | ||
class ActionText::RichText < ActiveRecord::Base | ||
self.table_name = "action_text_rich_texts" | ||
|
||
serialize :body, ActionText::Content | ||
delegate :to_s, :nil?, to: :body | ||
|
||
belongs_to :record, polymorphic: true, touch: true | ||
has_many_attached :embeds | ||
|
||
before_save do | ||
self.embeds = body.attachments.map(&:attachable) if body.present? | ||
end | ||
|
||
def to_plain_text | ||
body&.to_plain_text.to_s | ||
end | ||
|
||
delegate :blank?, :empty?, :present?, to: :to_plain_text | ||
end |
1 change: 1 addition & 0 deletions
1
actiontext/app/views/action_text/attachables/_missing_attachable.html.erb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<%= "☒" -%> |
Oops, something went wrong.