Import Action Text
This commit is contained in:
commit
0decd2ddc4
@ -9,6 +9,7 @@ AllCops:
|
||||
- 'actionpack/lib/action_dispatch/journey/parser.rb'
|
||||
- 'railties/test/fixtures/tmp/**/*'
|
||||
- 'actionmailbox/test/dummy/**/*'
|
||||
- 'actiontext/test/dummy/**/*'
|
||||
- 'node_modules/**/*'
|
||||
|
||||
Performance:
|
||||
@ -135,6 +136,7 @@ Style/FrozenStringLiteralComment:
|
||||
- 'actionpack/test/**/*.ruby'
|
||||
- 'activestorage/db/migrate/**/*.rb'
|
||||
- 'actionmailbox/db/migrate/**/*.rb'
|
||||
- 'actiontext/db/migrate/**/*.rb'
|
||||
|
||||
Style/RedundantFreeze:
|
||||
Enabled: true
|
||||
|
@ -57,7 +57,7 @@ env:
|
||||
- "JRUBY_OPTS='--dev -J-Xmx1024M'"
|
||||
matrix:
|
||||
- "GEM=actionpack,actioncable"
|
||||
- "GEM=actionmailer,activemodel,activesupport,actionview,activejob,activestorage,actionmailbox"
|
||||
- "GEM=actionmailer,activemodel,activesupport,actionview,activejob,activestorage,actionmailbox,actiontext"
|
||||
- "GEM=activesupport PRESERVE_TIMEZONES=1"
|
||||
- "GEM=activerecord:sqlite3"
|
||||
- "GEM=guides"
|
||||
|
@ -51,6 +51,12 @@ PATH
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actiontext (6.0.0.alpha)
|
||||
actionpack (= 6.0.0.alpha)
|
||||
activerecord (= 6.0.0.alpha)
|
||||
activestorage (= 6.0.0.alpha)
|
||||
activesupport (= 6.0.0.alpha)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.0.0.alpha)
|
||||
activesupport (= 6.0.0.alpha)
|
||||
builder (~> 3.1)
|
||||
@ -79,6 +85,7 @@ PATH
|
||||
actionmailbox (= 6.0.0.alpha)
|
||||
actionmailer (= 6.0.0.alpha)
|
||||
actionpack (= 6.0.0.alpha)
|
||||
actiontext (= 6.0.0.alpha)
|
||||
actionview (= 6.0.0.alpha)
|
||||
activejob (= 6.0.0.alpha)
|
||||
activemodel (= 6.0.0.alpha)
|
||||
|
5
actiontext/.gitignore
vendored
Normal file
5
actiontext/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/test/dummy/db/*.sqlite3
|
||||
/test/dummy/db/*.sqlite3-journal
|
||||
/test/dummy/log/*.log
|
||||
/test/dummy/tmp/
|
||||
/tmp/
|
3
actiontext/CHANGELOG.md
Normal file
3
actiontext/CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
||||
* Added to Rails.
|
||||
|
||||
*DHH*
|
21
actiontext/MIT-LICENSE
Normal file
21
actiontext/MIT-LICENSE
Normal file
@ -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.
|
67
actiontext/README.md
Normal file
67
actiontext/README.md
Normal file
@ -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).
|
13
actiontext/Rakefile
Normal file
13
actiontext/Rakefile
Normal file
@ -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
|
38
actiontext/actiontext.gemspec
Normal file
38
actiontext/actiontext.gemspec
Normal file
@ -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 = ["javan@javan.us", "sstephenson@gmail.com", "david@loudthinking.com"]
|
||||
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
|
30
actiontext/app/helpers/action_text/content_helper.rb
Normal file
30
actiontext/app/helpers/action_text/content_helper.rb
Normal file
@ -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
|
75
actiontext/app/helpers/action_text/tag_helper.rb
Normal file
75
actiontext/app/helpers/action_text/tag_helper.rb
Normal file
@ -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
|
45
actiontext/app/javascript/actiontext/attachment_upload.js
Normal file
45
actiontext/app/javascript/actiontext/attachment_upload.js
Normal file
@ -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
|
||||
}
|
||||
}
|
11
actiontext/app/javascript/actiontext/index.js
Normal file
11
actiontext/app/javascript/actiontext/index.js
Normal file
@ -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()
|
||||
}
|
||||
})
|
25
actiontext/app/models/action_text/rich_text.rb
Normal file
25
actiontext/app/models/action_text/rich_text.rb
Normal file
@ -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
|
@ -0,0 +1 @@
|
||||
<%= "☒" -%>
|
@ -0,0 +1,8 @@
|
||||
<figure class="attachment attachment--preview">
|
||||
<%= image_tag(remote_image.url, width: remote_image.width, height: remote_image.height) %>
|
||||
<% if caption = remote_image.try(:caption) %>
|
||||
<figcaption class="attachment__caption">
|
||||
<%= caption %>
|
||||
</figcaption>
|
||||
<% end %>
|
||||
</figure>
|
@ -0,0 +1,3 @@
|
||||
<div class="attachment-gallery attachment-gallery--<%= attachment_gallery.size %>">
|
||||
<%= yield %>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
<div class="trix-content">
|
||||
<%= render_action_text_content(content) %>
|
||||
</div>
|
14
actiontext/app/views/active_storage/blobs/_blob.html.erb
Normal file
14
actiontext/app/views/active_storage/blobs/_blob.html.erb
Normal file
@ -0,0 +1,14 @@
|
||||
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
||||
<% if blob.representable? %>
|
||||
<%= image_tag blob.representation(resize_to_fit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
|
||||
<% end %>
|
||||
|
||||
<figcaption class="attachment__caption">
|
||||
<% if caption = blob.try(:caption) %>
|
||||
<%= caption %>
|
||||
<% else %>
|
||||
<span class="attachment__name"><%= blob.filename %></span>
|
||||
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
|
||||
<% end %>
|
||||
</figcaption>
|
||||
</figure>
|
5
actiontext/bin/test
Executable file
5
actiontext/bin/test
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
COMPONENT_ROOT = File.expand_path("..", __dir__)
|
||||
require_relative "../../tools/test"
|
29
actiontext/bin/webpack
Executable file
29
actiontext/bin/webpack
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'webpack' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 150))
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("webpacker", "webpack")
|
29
actiontext/bin/webpack-dev-server
Executable file
29
actiontext/bin/webpack-dev-server
Executable file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# This file was generated by Bundler.
|
||||
#
|
||||
# The application 'webpack-dev-server' is installed as part of a gem, and
|
||||
# this file is here to facilitate running it.
|
||||
#
|
||||
|
||||
require "pathname"
|
||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
||||
Pathname.new(__FILE__).realpath)
|
||||
|
||||
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
||||
|
||||
if File.file?(bundle_binstub)
|
||||
if /This file was generated by Bundler/.match?(File.read(bundle_binstub, 150))
|
||||
load(bundle_binstub)
|
||||
else
|
||||
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||
end
|
||||
end
|
||||
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
load Gem.bin_path("webpacker", "webpack-dev-server")
|
@ -0,0 +1,14 @@
|
||||
class CreateActionTextTables < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :action_text_rich_texts do |t|
|
||||
t.string :name, null: false
|
||||
t.text :body, limit: 16777215
|
||||
t.references :record, null: false, polymorphic: true, index: false
|
||||
|
||||
t.datetime :created_at, null: false
|
||||
t.datetime :updated_at, null: false
|
||||
|
||||
t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
|
||||
end
|
||||
end
|
||||
end
|
37
actiontext/lib/action_text.rb
Normal file
37
actiontext/lib/action_text.rb
Normal file
@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support"
|
||||
require "active_support/rails"
|
||||
|
||||
require "nokogiri"
|
||||
|
||||
module ActionText
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Attachable
|
||||
autoload :AttachmentGallery
|
||||
autoload :Attachment
|
||||
autoload :Attribute
|
||||
autoload :Content
|
||||
autoload :Fragment
|
||||
autoload :HtmlConversion
|
||||
autoload :PlainTextConversion
|
||||
autoload :Serialization
|
||||
autoload :TrixAttachment
|
||||
|
||||
module Attachables
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :ContentAttachment
|
||||
autoload :MissingAttachable
|
||||
autoload :RemoteImage
|
||||
end
|
||||
|
||||
module Attachments
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :Caching
|
||||
autoload :Minification
|
||||
autoload :TrixConversion
|
||||
end
|
||||
end
|
82
actiontext/lib/action_text/attachable.rb
Normal file
82
actiontext/lib/action_text/attachable.rb
Normal file
@ -0,0 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attachable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
LOCATOR_NAME = "attachable"
|
||||
|
||||
class << self
|
||||
def from_node(node)
|
||||
if attachable = attachable_from_sgid(node["sgid"])
|
||||
attachable
|
||||
elsif attachable = ActionText::Attachables::ContentAttachment.from_node(node)
|
||||
attachable
|
||||
elsif attachable = ActionText::Attachables::RemoteImage.from_node(node)
|
||||
attachable
|
||||
else
|
||||
ActionText::Attachables::MissingAttachable
|
||||
end
|
||||
end
|
||||
|
||||
def from_attachable_sgid(sgid, options = {})
|
||||
method = sgid.is_a?(Array) ? :locate_many_signed : :locate_signed
|
||||
record = GlobalID::Locator.public_send(method, sgid, options.merge(for: LOCATOR_NAME))
|
||||
record || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
private
|
||||
def attachable_from_sgid(sgid)
|
||||
from_attachable_sgid(sgid)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def from_attachable_sgid(sgid)
|
||||
ActionText::Attachable.from_attachable_sgid(sgid, only: self)
|
||||
end
|
||||
end
|
||||
|
||||
def attachable_sgid
|
||||
to_sgid(expires_in: nil, for: LOCATOR_NAME).to_s
|
||||
end
|
||||
|
||||
def attachable_content_type
|
||||
try(:content_type) || "application/octet-stream"
|
||||
end
|
||||
|
||||
def attachable_filename
|
||||
filename.to_s if respond_to?(:filename)
|
||||
end
|
||||
|
||||
def attachable_filesize
|
||||
try(:byte_size) || try(:filesize)
|
||||
end
|
||||
|
||||
def attachable_metadata
|
||||
try(:metadata) || {}
|
||||
end
|
||||
|
||||
def previewable_attachable?
|
||||
false
|
||||
end
|
||||
|
||||
def as_json(*)
|
||||
super.merge(attachable_sgid: attachable_sgid)
|
||||
end
|
||||
|
||||
def to_rich_text_attributes(attributes = {})
|
||||
attributes.dup.tap do |attrs|
|
||||
attrs[:sgid] = attachable_sgid
|
||||
attrs[:content_type] = attachable_content_type
|
||||
attrs[:previewable] = true if previewable_attachable?
|
||||
attrs[:filename] = attachable_filename
|
||||
attrs[:filesize] = attachable_filesize
|
||||
attrs[:width] = attachable_metadata[:width]
|
||||
attrs[:height] = attachable_metadata[:height]
|
||||
end.compact
|
||||
end
|
||||
end
|
||||
end
|
38
actiontext/lib/action_text/attachables/content_attachment.rb
Normal file
38
actiontext/lib/action_text/attachables/content_attachment.rb
Normal file
@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attachables
|
||||
class ContentAttachment
|
||||
include ActiveModel::Model
|
||||
|
||||
def self.from_node(node)
|
||||
if node["content-type"]
|
||||
if matches = node["content-type"].match(/vnd\.rubyonrails\.(.+)\.html/)
|
||||
attachment = new(name: matches[1])
|
||||
attachment if attachment.valid?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :name
|
||||
validates_inclusion_of :name, in: %w( horizontal-rule )
|
||||
|
||||
def attachable_plain_text_representation(caption)
|
||||
case name
|
||||
when "horizontal-rule"
|
||||
" ┄ "
|
||||
else
|
||||
" "
|
||||
end
|
||||
end
|
||||
|
||||
def to_partial_path
|
||||
"action_text/attachables/content_attachment"
|
||||
end
|
||||
|
||||
def to_trix_content_attachment_partial_path
|
||||
"action_text/attachables/content_attachments/#{name.underscore}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
actiontext/lib/action_text/attachables/missing_attachable.rb
Normal file
13
actiontext/lib/action_text/attachables/missing_attachable.rb
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attachables
|
||||
module MissingAttachable
|
||||
extend ActiveModel::Naming
|
||||
|
||||
def self.to_partial_path
|
||||
"action_text/attachables/missing_attachable"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
46
actiontext/lib/action_text/attachables/remote_image.rb
Normal file
46
actiontext/lib/action_text/attachables/remote_image.rb
Normal file
@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attachables
|
||||
class RemoteImage
|
||||
extend ActiveModel::Naming
|
||||
|
||||
class << self
|
||||
def from_node(node)
|
||||
if node["url"] && content_type_is_image?(node["content-type"])
|
||||
new(attributes_from_node(node))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def content_type_is_image?(content_type)
|
||||
content_type.to_s =~ /^image(\/.+|$)/
|
||||
end
|
||||
|
||||
def attributes_from_node(node)
|
||||
{ url: node["url"],
|
||||
content_type: node["content-type"],
|
||||
width: node["width"],
|
||||
height: node["height"] }
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :url, :content_type, :width, :height
|
||||
|
||||
def initialize(attributes = {})
|
||||
@url = attributes[:url]
|
||||
@content_type = attributes[:content_type]
|
||||
@width = attributes[:width]
|
||||
@height = attributes[:height]
|
||||
end
|
||||
|
||||
def attachable_plain_text_representation(caption)
|
||||
"[#{caption || "Image"}]"
|
||||
end
|
||||
|
||||
def to_partial_path
|
||||
"action_text/attachables/remote_image"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
103
actiontext/lib/action_text/attachment.rb
Normal file
103
actiontext/lib/action_text/attachment.rb
Normal file
@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
class Attachment
|
||||
include Attachments::TrixConversion, Attachments::Minification, Attachments::Caching
|
||||
|
||||
TAG_NAME = "action-text-attachment"
|
||||
SELECTOR = TAG_NAME
|
||||
ATTRIBUTES = %w( sgid content-type url href filename filesize width height previewable presentation caption )
|
||||
|
||||
class << self
|
||||
def fragment_by_canonicalizing_attachments(content)
|
||||
fragment_by_minifying_attachments(fragment_by_converting_trix_attachments(content))
|
||||
end
|
||||
|
||||
def from_node(node, attachable = nil)
|
||||
new(node, attachable || ActionText::Attachable.from_node(node))
|
||||
end
|
||||
|
||||
def from_attachables(attachables)
|
||||
Array(attachables).map { |attachable| from_attachable(attachable) }.compact
|
||||
end
|
||||
|
||||
def from_attachable(attachable, attributes = {})
|
||||
if node = node_from_attributes(attachable.to_rich_text_attributes(attributes))
|
||||
new(node, attachable)
|
||||
end
|
||||
end
|
||||
|
||||
def from_attributes(attributes, attachable = nil)
|
||||
if node = node_from_attributes(attributes)
|
||||
from_node(node, attachable)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def node_from_attributes(attributes)
|
||||
if attributes = process_attributes(attributes).presence
|
||||
ActionText::HtmlConversion.create_element(TAG_NAME, attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def process_attributes(attributes)
|
||||
attributes.transform_keys { |key| key.to_s.underscore.dasherize }.slice(*ATTRIBUTES)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :node, :attachable
|
||||
|
||||
delegate :to_param, to: :attachable
|
||||
delegate_missing_to :attachable
|
||||
|
||||
def initialize(node, attachable)
|
||||
@node = node
|
||||
@attachable = attachable
|
||||
end
|
||||
|
||||
def caption
|
||||
node_attributes["caption"].presence
|
||||
end
|
||||
|
||||
def full_attributes
|
||||
node_attributes.merge(attachable_attributes).merge(sgid_attributes)
|
||||
end
|
||||
|
||||
def with_full_attributes
|
||||
self.class.from_attributes(full_attributes, attachable)
|
||||
end
|
||||
|
||||
def to_plain_text
|
||||
if respond_to?(:attachable_plain_text_representation)
|
||||
attachable_plain_text_representation(caption)
|
||||
else
|
||||
caption.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def to_html
|
||||
HtmlConversion.node_to_html(node)
|
||||
end
|
||||
|
||||
def to_s
|
||||
to_html
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class.name} attachable=#{attachable.inspect}>"
|
||||
end
|
||||
|
||||
private
|
||||
def node_attributes
|
||||
@node_attributes ||= ATTRIBUTES.map { |name| [ name.underscore, node[name] ] }.to_h.compact
|
||||
end
|
||||
|
||||
def attachable_attributes
|
||||
@attachable_attributes ||= (attachable.try(:to_rich_text_attributes) || {}).stringify_keys
|
||||
end
|
||||
|
||||
def sgid_attributes
|
||||
@sgid_attributes ||= node_attributes.slice("sgid").presence || attachable_attributes.slice("sgid")
|
||||
end
|
||||
end
|
||||
end
|
65
actiontext/lib/action_text/attachment_gallery.rb
Normal file
65
actiontext/lib/action_text/attachment_gallery.rb
Normal file
@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
class AttachmentGallery
|
||||
include ActiveModel::Model
|
||||
|
||||
class << self
|
||||
def fragment_by_canonicalizing_attachment_galleries(content)
|
||||
fragment_by_replacing_attachment_gallery_nodes(content) do |node|
|
||||
"<#{TAG_NAME}>#{node.inner_html}</#{TAG_NAME}>"
|
||||
end
|
||||
end
|
||||
|
||||
def fragment_by_replacing_attachment_gallery_nodes(content)
|
||||
Fragment.wrap(content).update do |source|
|
||||
find_attachment_gallery_nodes(source).each do |node|
|
||||
node.replace(yield(node).to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_attachment_gallery_nodes(content)
|
||||
Fragment.wrap(content).find_all(SELECTOR).select do |node|
|
||||
node.children.all? do |child|
|
||||
if child.text?
|
||||
child.text =~ /\A(\n|\ )*\z/
|
||||
else
|
||||
child.matches? ATTACHMENT_SELECTOR
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def from_node(node)
|
||||
new(node)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :node
|
||||
|
||||
def initialize(node)
|
||||
@node = node
|
||||
end
|
||||
|
||||
def attachments
|
||||
@attachments ||= node.css(ATTACHMENT_SELECTOR).map do |node|
|
||||
ActionText::Attachment.from_node(node).with_full_attributes
|
||||
end
|
||||
end
|
||||
|
||||
def size
|
||||
attachments.size
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class.name} size=#{size.inspect}>"
|
||||
end
|
||||
|
||||
TAG_NAME = "div"
|
||||
ATTACHMENT_SELECTOR = "#{ActionText::Attachment::SELECTOR}[presentation=gallery]"
|
||||
SELECTOR = "#{TAG_NAME}:has(#{ATTACHMENT_SELECTOR} + #{ATTACHMENT_SELECTOR})"
|
||||
|
||||
private_constant :TAG_NAME, :ATTACHMENT_SELECTOR, :SELECTOR
|
||||
end
|
||||
end
|
16
actiontext/lib/action_text/attachments/caching.rb
Normal file
16
actiontext/lib/action_text/attachments/caching.rb
Normal file
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attachments
|
||||
module Caching
|
||||
def cache_key(*args)
|
||||
[self.class.name, cache_digest, *attachable.cache_key(*args)].join("/")
|
||||
end
|
||||
|
||||
private
|
||||
def cache_digest
|
||||
Digest::SHA256.hexdigest(node.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
actiontext/lib/action_text/attachments/minification.rb
Normal file
17
actiontext/lib/action_text/attachments/minification.rb
Normal file
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attachments
|
||||
module Minification
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def fragment_by_minifying_attachments(content)
|
||||
Fragment.wrap(content).replace(ActionText::Attachment::SELECTOR) do |node|
|
||||
node.tap { |n| n.inner_html = "" }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
actiontext/lib/action_text/attachments/trix_conversion.rb
Normal file
34
actiontext/lib/action_text/attachments/trix_conversion.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attachments
|
||||
module TrixConversion
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def fragment_by_converting_trix_attachments(content)
|
||||
Fragment.wrap(content).replace(TrixAttachment::SELECTOR) do |node|
|
||||
from_trix_attachment(TrixAttachment.new(node))
|
||||
end
|
||||
end
|
||||
|
||||
def from_trix_attachment(trix_attachment)
|
||||
from_attributes(trix_attachment.attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def to_trix_attachment(content = trix_attachment_content)
|
||||
attributes = full_attributes.dup
|
||||
attributes["content"] = content if content
|
||||
TrixAttachment.from_attributes(attributes)
|
||||
end
|
||||
|
||||
private
|
||||
def trix_attachment_content
|
||||
if partial_path = attachable.try(:to_trix_content_attachment_partial_path)
|
||||
ActionText::Content.renderer.render(partial: partial_path, object: self, as: model_name.element)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
48
actiontext/lib/action_text/attribute.rb
Normal file
48
actiontext/lib/action_text/attribute.rb
Normal file
@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Attribute
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
# Provides access to a dependent RichText model that holds the body and attachments for a single named rich text attribute.
|
||||
# This dependent attribute is lazily instantiated and will be auto-saved when it's been changed. Example:
|
||||
#
|
||||
# class Message < ActiveRecord::Base
|
||||
# has_rich_text :content
|
||||
# end
|
||||
#
|
||||
# message = Message.create!(content: "<h1>Funny times!</h1>")
|
||||
# message.content.to_s # => "<h1>Funny times!</h1>"
|
||||
# message.content.to_plain_text # => "Funny times!"
|
||||
#
|
||||
# The dependent RichText model will also automatically process attachments links as sent via the Trix-powered editor.
|
||||
# These attachments are associated with the RichText model using Active Storage.
|
||||
#
|
||||
# If you wish to preload the dependent RichText model, you can use the named scope:
|
||||
#
|
||||
# Message.all.with_rich_text_content # Avoids N+1 queries when you just want the body, not the attachments.
|
||||
# Message.all.with_rich_text_content_and_embeds # Avoids N+1 queries when you just want the body and attachments.
|
||||
def has_rich_text(name)
|
||||
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
||||
def #{name}
|
||||
self.rich_text_#{name} ||= ActionText::RichText.new(name: "#{name}", record: self)
|
||||
end
|
||||
|
||||
def #{name}=(body)
|
||||
self.#{name}.body = body
|
||||
end
|
||||
CODE
|
||||
|
||||
has_one :"rich_text_#{name}", -> { where(name: name) }, class_name: "ActionText::RichText", as: :record, inverse_of: :record, dependent: :destroy
|
||||
|
||||
scope :"with_rich_text_#{name}", -> { includes("rich_text_#{name}") }
|
||||
scope :"with_rich_text_#{name}_and_embeds", -> { includes("rich_text_#{name}": { embeds_attachments: :blob }) }
|
||||
|
||||
after_save do
|
||||
public_send(name).save if public_send(name).changed?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
132
actiontext/lib/action_text/content.rb
Normal file
132
actiontext/lib/action_text/content.rb
Normal file
@ -0,0 +1,132 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/core_ext/module/attribute_accessors_per_thread"
|
||||
|
||||
module ActionText
|
||||
class Content
|
||||
include Serialization
|
||||
|
||||
thread_cattr_accessor :renderer
|
||||
|
||||
attr_reader :fragment
|
||||
|
||||
delegate :blank?, :empty?, :html_safe, :present?, to: :to_html # Delegating to to_html to avoid including the layout
|
||||
|
||||
class << self
|
||||
def fragment_by_canonicalizing_content(content)
|
||||
fragment = ActionText::Attachment.fragment_by_canonicalizing_attachments(content)
|
||||
fragment = ActionText::AttachmentGallery.fragment_by_canonicalizing_attachment_galleries(fragment)
|
||||
fragment
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(content = nil, options = {})
|
||||
options.with_defaults! canonicalize: true
|
||||
|
||||
if options[:canonicalize]
|
||||
@fragment = self.class.fragment_by_canonicalizing_content(content)
|
||||
else
|
||||
@fragment = ActionText::Fragment.wrap(content)
|
||||
end
|
||||
end
|
||||
|
||||
def links
|
||||
@links ||= fragment.find_all("a[href]").map { |a| a["href"] }.uniq
|
||||
end
|
||||
|
||||
def attachments
|
||||
@attachments ||= attachment_nodes.map do |node|
|
||||
attachment_for_node(node)
|
||||
end
|
||||
end
|
||||
|
||||
def attachment_galleries
|
||||
@attachment_galleries ||= attachment_gallery_nodes.map do |node|
|
||||
attachment_gallery_for_node(node)
|
||||
end
|
||||
end
|
||||
|
||||
def gallery_attachments
|
||||
@gallery_attachments ||= attachment_galleries.flat_map(&:attachments)
|
||||
end
|
||||
|
||||
def attachables
|
||||
@attachables ||= attachment_nodes.map do |node|
|
||||
ActionText::Attachable.from_node(node)
|
||||
end
|
||||
end
|
||||
|
||||
def append_attachables(attachables)
|
||||
attachments = ActionText::Attachment.from_attachables(attachables)
|
||||
self.class.new([self.to_s.presence, *attachments].compact.join("\n"))
|
||||
end
|
||||
|
||||
def render_attachments(**options, &block)
|
||||
content = fragment.replace(ActionText::Attachment::SELECTOR) do |node|
|
||||
block.call(attachment_for_node(node, **options))
|
||||
end
|
||||
self.class.new(content, canonicalize: false)
|
||||
end
|
||||
|
||||
def render_attachment_galleries(&block)
|
||||
content = ActionText::AttachmentGallery.fragment_by_replacing_attachment_gallery_nodes(fragment) do |node|
|
||||
block.call(attachment_gallery_for_node(node))
|
||||
end
|
||||
self.class.new(content, canonicalize: false)
|
||||
end
|
||||
|
||||
def to_plain_text
|
||||
render_attachments(with_full_attributes: false, &:to_plain_text).fragment.to_plain_text
|
||||
end
|
||||
|
||||
def to_trix_html
|
||||
render_attachments(&:to_trix_attachment).to_html
|
||||
end
|
||||
|
||||
def to_html
|
||||
fragment.to_html
|
||||
end
|
||||
|
||||
def to_rendered_html_with_layout
|
||||
renderer.render(partial: "action_text/content/layout", locals: { content: self })
|
||||
end
|
||||
|
||||
def to_s
|
||||
to_rendered_html_with_layout
|
||||
end
|
||||
|
||||
def as_json(*)
|
||||
to_html
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class.name} #{to_s.truncate(25).inspect}>"
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
if other.is_a?(self.class)
|
||||
to_s == other.to_s
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def attachment_nodes
|
||||
@attachment_nodes ||= fragment.find_all(ActionText::Attachment::SELECTOR)
|
||||
end
|
||||
|
||||
def attachment_gallery_nodes
|
||||
@attachment_gallery_nodes ||= ActionText::AttachmentGallery.find_attachment_gallery_nodes(fragment)
|
||||
end
|
||||
|
||||
def attachment_for_node(node, with_full_attributes: true)
|
||||
attachment = ActionText::Attachment.from_node(node)
|
||||
with_full_attributes ? attachment.with_full_attributes : attachment
|
||||
end
|
||||
|
||||
def attachment_gallery_for_node(node)
|
||||
ActionText::AttachmentGallery.from_node(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks :action_text_content, ActionText::Content
|
47
actiontext/lib/action_text/engine.rb
Normal file
47
actiontext/lib/action_text/engine.rb
Normal file
@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails"
|
||||
require "action_controller/railtie"
|
||||
require "active_record/railtie"
|
||||
require "active_storage/engine"
|
||||
|
||||
require "action_text"
|
||||
|
||||
module ActionText
|
||||
class Engine < Rails::Engine
|
||||
isolate_namespace ActionText
|
||||
config.eager_load_namespaces << ActionText
|
||||
|
||||
initializer "action_text.attribute" do
|
||||
ActiveSupport.on_load(:active_record) do
|
||||
include ActionText::Attribute
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_text.attachable" do
|
||||
ActiveSupport.on_load(:active_storage_blob) do
|
||||
include ActionText::Attachable
|
||||
|
||||
def previewable_attachable?
|
||||
representable?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_text.helper" do
|
||||
ActiveSupport.on_load(:action_controller_base) do
|
||||
helper ActionText::Engine.helpers
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_text.renderer" do
|
||||
ActiveSupport.on_load(:action_text_content) do
|
||||
self.renderer ||= ApplicationController.renderer
|
||||
end
|
||||
|
||||
ActiveSupport.on_load(:action_controller_base) do
|
||||
before_action { ActionText::Content.renderer = ActionText::Content.renderer.new(request.env) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
57
actiontext/lib/action_text/fragment.rb
Normal file
57
actiontext/lib/action_text/fragment.rb
Normal file
@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
class Fragment
|
||||
class << self
|
||||
def wrap(fragment_or_html)
|
||||
case fragment_or_html
|
||||
when self
|
||||
fragment_or_html
|
||||
when Nokogiri::HTML::DocumentFragment
|
||||
new(fragment_or_html)
|
||||
else
|
||||
from_html(fragment_or_html)
|
||||
end
|
||||
end
|
||||
|
||||
def from_html(html)
|
||||
new(ActionText::HtmlConversion.fragment_for_html(html.to_s.strip))
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :source
|
||||
|
||||
def initialize(source)
|
||||
@source = source
|
||||
end
|
||||
|
||||
def find_all(selector)
|
||||
source.css(selector)
|
||||
end
|
||||
|
||||
def update
|
||||
yield source = self.source.clone
|
||||
self.class.new(source)
|
||||
end
|
||||
|
||||
def replace(selector)
|
||||
update do |source|
|
||||
source.css(selector).each do |node|
|
||||
node.replace(yield(node).to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_plain_text
|
||||
@plain_text ||= PlainTextConversion.node_to_plain_text(source)
|
||||
end
|
||||
|
||||
def to_html
|
||||
@html ||= HtmlConversion.node_to_html(source)
|
||||
end
|
||||
|
||||
def to_s
|
||||
to_html
|
||||
end
|
||||
end
|
||||
end
|
17
actiontext/lib/action_text/gem_version.rb
Normal file
17
actiontext/lib/action_text/gem_version.rb
Normal file
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
# Returns the currently-loaded version of Action Text as a <tt>Gem::Version</tt>.
|
||||
def self.gem_version
|
||||
Gem::Version.new VERSION::STRING
|
||||
end
|
||||
|
||||
module VERSION
|
||||
MAJOR = 6
|
||||
MINOR = 0
|
||||
TINY = 0
|
||||
PRE = "alpha"
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
||||
end
|
||||
end
|
24
actiontext/lib/action_text/html_conversion.rb
Normal file
24
actiontext/lib/action_text/html_conversion.rb
Normal file
@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module HtmlConversion
|
||||
extend self
|
||||
|
||||
def node_to_html(node)
|
||||
node.to_html(save_with: Nokogiri::XML::Node::SaveOptions::AS_HTML)
|
||||
end
|
||||
|
||||
def fragment_for_html(html)
|
||||
document.fragment(html)
|
||||
end
|
||||
|
||||
def create_element(tag_name, attributes = {})
|
||||
document.create_element(tag_name, attributes)
|
||||
end
|
||||
|
||||
private
|
||||
def document
|
||||
Nokogiri::HTML::Document.new.tap { |doc| doc.encoding = "UTF-8" }
|
||||
end
|
||||
end
|
||||
end
|
81
actiontext/lib/action_text/plain_text_conversion.rb
Normal file
81
actiontext/lib/action_text/plain_text_conversion.rb
Normal file
@ -0,0 +1,81 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module PlainTextConversion
|
||||
extend self
|
||||
|
||||
def node_to_plain_text(node)
|
||||
remove_trailing_newlines(plain_text_for_node(node))
|
||||
end
|
||||
|
||||
private
|
||||
def plain_text_for_node(node, index = 0)
|
||||
if respond_to?(plain_text_method_for_node(node), true)
|
||||
send(plain_text_method_for_node(node), node, index)
|
||||
else
|
||||
plain_text_for_node_children(node)
|
||||
end
|
||||
end
|
||||
|
||||
def plain_text_for_node_children(node)
|
||||
node.children.each_with_index.map do |child, index|
|
||||
plain_text_for_node(child, index)
|
||||
end.compact.join("")
|
||||
end
|
||||
|
||||
def plain_text_method_for_node(node)
|
||||
:"plain_text_for_#{node.name}_node"
|
||||
end
|
||||
|
||||
def plain_text_for_block(node, index = 0)
|
||||
"#{remove_trailing_newlines(plain_text_for_node_children(node))}\n\n"
|
||||
end
|
||||
|
||||
%i[ h1 p ul ol ].each do |element|
|
||||
alias_method :"plain_text_for_#{element}_node", :plain_text_for_block
|
||||
end
|
||||
|
||||
def plain_text_for_br_node(node, index)
|
||||
"\n"
|
||||
end
|
||||
|
||||
def plain_text_for_text_node(node, index)
|
||||
remove_trailing_newlines(node.text)
|
||||
end
|
||||
|
||||
def plain_text_for_div_node(node, index)
|
||||
"#{remove_trailing_newlines(plain_text_for_node_children(node))}\n"
|
||||
end
|
||||
|
||||
def plain_text_for_figcaption_node(node, index)
|
||||
"[#{remove_trailing_newlines(plain_text_for_node_children(node))}]"
|
||||
end
|
||||
|
||||
def plain_text_for_blockquote_node(node, index)
|
||||
text = plain_text_for_block(node)
|
||||
text.sub(/\A(\s*)(.+?)(\s*)\Z/m, '\1“\2”\3')
|
||||
end
|
||||
|
||||
def plain_text_for_li_node(node, index)
|
||||
bullet = bullet_for_li_node(node, index)
|
||||
text = remove_trailing_newlines(plain_text_for_node_children(node))
|
||||
"#{bullet} #{text}\n"
|
||||
end
|
||||
|
||||
def remove_trailing_newlines(text)
|
||||
text.chomp("")
|
||||
end
|
||||
|
||||
def bullet_for_li_node(node, index)
|
||||
if list_node_name_for_li_node(node) == "ol"
|
||||
"#{index + 1}."
|
||||
else
|
||||
"•"
|
||||
end
|
||||
end
|
||||
|
||||
def list_node_name_for_li_node(node)
|
||||
node.ancestors.lazy.map(&:name).grep(/^[uo]l$/).first
|
||||
end
|
||||
end
|
||||
end
|
34
actiontext/lib/action_text/serialization.rb
Normal file
34
actiontext/lib/action_text/serialization.rb
Normal file
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
module Serialization
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def load(content)
|
||||
new(content) if content
|
||||
end
|
||||
|
||||
def dump(content)
|
||||
case content
|
||||
when nil
|
||||
nil
|
||||
when self
|
||||
content.to_html
|
||||
else
|
||||
new(content).to_html
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Marshal compatibility
|
||||
|
||||
class_methods do
|
||||
alias_method :_load, :load
|
||||
end
|
||||
|
||||
def _dump(*)
|
||||
self.class.dump(self)
|
||||
end
|
||||
end
|
||||
end
|
92
actiontext/lib/action_text/trix_attachment.rb
Normal file
92
actiontext/lib/action_text/trix_attachment.rb
Normal file
@ -0,0 +1,92 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionText
|
||||
class TrixAttachment
|
||||
TAG_NAME = "figure"
|
||||
SELECTOR = "[data-trix-attachment]"
|
||||
|
||||
COMPOSED_ATTRIBUTES = %w( caption presentation )
|
||||
ATTRIBUTES = %w( sgid contentType url href filename filesize width height previewable content ) + COMPOSED_ATTRIBUTES
|
||||
ATTRIBUTE_TYPES = {
|
||||
"previewable" => ->(value) { value.to_s == "true" },
|
||||
"filesize" => ->(value) { Integer(value.to_s) rescue value },
|
||||
"width" => ->(value) { Integer(value.to_s) rescue nil },
|
||||
"height" => ->(value) { Integer(value.to_s) rescue nil },
|
||||
:default => ->(value) { value.to_s }
|
||||
}
|
||||
|
||||
class << self
|
||||
def from_attributes(attributes)
|
||||
attributes = process_attributes(attributes)
|
||||
|
||||
trix_attachment_attributes = attributes.except(*COMPOSED_ATTRIBUTES)
|
||||
trix_attributes = attributes.slice(*COMPOSED_ATTRIBUTES)
|
||||
|
||||
node = ActionText::HtmlConversion.create_element(TAG_NAME)
|
||||
node["data-trix-attachment"] = JSON.generate(trix_attachment_attributes)
|
||||
node["data-trix-attributes"] = JSON.generate(trix_attributes) if trix_attributes.any?
|
||||
|
||||
new(node)
|
||||
end
|
||||
|
||||
private
|
||||
def process_attributes(attributes)
|
||||
typecast_attribute_values(transform_attribute_keys(attributes))
|
||||
end
|
||||
|
||||
def transform_attribute_keys(attributes)
|
||||
attributes.transform_keys { |key| key.to_s.underscore.camelize(:lower) }
|
||||
end
|
||||
|
||||
def typecast_attribute_values(attributes)
|
||||
attributes.map do |key, value|
|
||||
typecast = ATTRIBUTE_TYPES[key] || ATTRIBUTE_TYPES[:default]
|
||||
[key, typecast.call(value)]
|
||||
end.to_h
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :node
|
||||
|
||||
def initialize(node)
|
||||
@node = node
|
||||
end
|
||||
|
||||
def attributes
|
||||
@attributes ||= attachment_attributes.merge(composed_attributes).slice(*ATTRIBUTES)
|
||||
end
|
||||
|
||||
def to_html
|
||||
ActionText::HtmlConversion.node_to_html(node)
|
||||
end
|
||||
|
||||
def to_s
|
||||
to_html
|
||||
end
|
||||
|
||||
private
|
||||
def attachment_attributes
|
||||
read_json_object_attribute("data-trix-attachment")
|
||||
end
|
||||
|
||||
def composed_attributes
|
||||
read_json_object_attribute("data-trix-attributes")
|
||||
end
|
||||
|
||||
def read_json_object_attribute(name)
|
||||
read_json_attribute(name) || {}
|
||||
end
|
||||
|
||||
def read_json_attribute(name)
|
||||
if value = node[name]
|
||||
begin
|
||||
JSON.parse(value)
|
||||
rescue => e
|
||||
Rails.logger.error "[#{self.class.name}] Couldn't parse JSON #{value} from NODE #{node.inspect}"
|
||||
Rails.logger.error "[#{self.class.name}] Failed with #{e.class}: #{e.backtrace}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
actiontext/lib/action_text/version.rb
Normal file
10
actiontext/lib/action_text/version.rb
Normal file
@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "gem_version"
|
||||
|
||||
module ActionText
|
||||
# Returns the currently-loaded version of Action Text as a <tt>Gem::Version</tt>.
|
||||
def self.version
|
||||
gem_version
|
||||
end
|
||||
end
|
20
actiontext/lib/tasks/actiontext.rake
Normal file
20
actiontext/lib/tasks/actiontext.rake
Normal file
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
namespace :action_text do
|
||||
# Prevent migration installation task from showing up twice.
|
||||
Rake::Task["install:migrations"].clear_comments
|
||||
|
||||
desc "Copy over the migration, stylesheet, and JavaScript files"
|
||||
task install: %w( environment run_installer copy_migrations )
|
||||
|
||||
task :run_installer do
|
||||
installer_template = File.expand_path("../templates/installer.rb", __dir__)
|
||||
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{installer_template}"
|
||||
end
|
||||
|
||||
task :copy_migrations do
|
||||
Rake::Task["active_storage:install:migrations"].invoke
|
||||
Rake::Task["railties:install:migrations"].reenable # Otherwise you can't run 2 migration copy tasks in one invocation
|
||||
Rake::Task["action_text:install:migrations"].invoke
|
||||
end
|
||||
end
|
36
actiontext/lib/templates/actiontext.scss
Normal file
36
actiontext/lib/templates/actiontext.scss
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
|
||||
// the trix-editor content (whether displayed or under editing). Feel free to incorporate this
|
||||
// inclusion directly in any other asset bundle and remove this file.
|
||||
//
|
||||
//= require trix/dist/trix
|
||||
|
||||
// We need to override trix.css’s image gallery styles to accommodate the
|
||||
// <action-text-attachment> element we wrap around attachments. Otherwise,
|
||||
// images in galleries will be squished by the max-width: 33%; rule.
|
||||
.trix-content {
|
||||
.attachment-gallery {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex: 1 0 33%;
|
||||
padding: 0 0.5em;
|
||||
max-width: 33%;
|
||||
}
|
||||
|
||||
&.attachment-gallery--2,
|
||||
&.attachment-gallery--4 {
|
||||
> action-text-attachment,
|
||||
> .attachment {
|
||||
flex-basis: 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action-text-attachment {
|
||||
.attachment {
|
||||
padding: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
4
actiontext/lib/templates/fixtures.yml
Normal file
4
actiontext/lib/templates/fixtures.yml
Normal file
@ -0,0 +1,4 @@
|
||||
# one:
|
||||
# record: name_of_fixture (ClassOfFixture)
|
||||
# name: content
|
||||
# body: <p>In a <i>million</i> stars!</p>
|
22
actiontext/lib/templates/installer.rb
Normal file
22
actiontext/lib/templates/installer.rb
Normal file
@ -0,0 +1,22 @@
|
||||
say "Copying actiontext.scss to app/assets/stylesheets"
|
||||
copy_file "#{__dir__}/actiontext.scss", "app/assets/stylesheets/actiontext.scss"
|
||||
|
||||
say "Copying fixtures to test/fixtures/action_text/rich_texts.yml"
|
||||
copy_file "#{__dir__}/fixtures.yml", "test/fixtures/action_text/rich_texts.yml"
|
||||
|
||||
say "Copying blob rendering partial to app/views/active_storage/blobs/_blob.html.erb"
|
||||
copy_file "#{__dir__}/../../app/views/active_storage/blobs/_blob.html.erb",
|
||||
"app/views/active_storage/blobs/_blob.html.erb"
|
||||
|
||||
# FIXME: Replace with release version on release
|
||||
say "Installing JavaScript dependency"
|
||||
run "yarn add https://github.com/rails/actiontext"
|
||||
|
||||
APPLICATION_PACK_PATH = "app/javascript/packs/application.js"
|
||||
|
||||
if File.exists?(APPLICATION_PACK_PATH) && File.read(APPLICATION_PACK_PATH) !~ /import "actiontext"/
|
||||
say "Adding import to default JavaScript pack"
|
||||
append_to_file APPLICATION_PACK_PATH, <<-EOS
|
||||
import "actiontext"
|
||||
EOS
|
||||
end
|
27
actiontext/package.json
Normal file
27
actiontext/package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "actiontext",
|
||||
"version": "6.0.0-alpha",
|
||||
"description": "Edit and display rich text in Rails applications",
|
||||
"main": "app/javascript/actiontext/index.js",
|
||||
"files": [
|
||||
"app/javascript/actiontext/*.js"
|
||||
],
|
||||
"homepage": "http://rubyonrails.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rails/rails.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/rails/rails/issues"
|
||||
},
|
||||
"author": "Basecamp, LLC",
|
||||
"contributors": [
|
||||
"Javan Makhmali <javan@javan.us>",
|
||||
"Sam Stephenson <sstephenson@gmail.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"trix": ">=1.0.0",
|
||||
"activestorage": "6.0.0-alpha"
|
||||
}
|
||||
}
|
18
actiontext/test/dummy/.babelrc
Normal file
18
actiontext/test/dummy/.babelrc
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"presets": [
|
||||
["env", {
|
||||
"modules": false,
|
||||
"targets": {
|
||||
"browsers": "> 1%",
|
||||
"uglify": true
|
||||
},
|
||||
"useBuiltIns": true
|
||||
}]
|
||||
],
|
||||
|
||||
"plugins": [
|
||||
"syntax-dynamic-import",
|
||||
"transform-object-rest-spread",
|
||||
["transform-class-properties", { "spec": true }]
|
||||
]
|
||||
}
|
3
actiontext/test/dummy/.postcssrc.yml
Normal file
3
actiontext/test/dummy/.postcssrc.yml
Normal file
@ -0,0 +1,3 @@
|
||||
plugins:
|
||||
postcss-import: {}
|
||||
postcss-cssnext: {}
|
6
actiontext/test/dummy/Rakefile
Normal file
6
actiontext/test/dummy/Rakefile
Normal file
@ -0,0 +1,6 @@
|
||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require_relative 'config/application'
|
||||
|
||||
Rails.application.load_tasks
|
3
actiontext/test/dummy/app/assets/config/manifest.js
Normal file
3
actiontext/test/dummy/app/assets/config/manifest.js
Normal file
@ -0,0 +1,3 @@
|
||||
//= link_tree ../images
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
0
actiontext/test/dummy/app/assets/images/.keep
Normal file
0
actiontext/test/dummy/app/assets/images/.keep
Normal file
16
actiontext/test/dummy/app/assets/stylesheets/application.css
Normal file
16
actiontext/test/dummy/app/assets/stylesheets/application.css
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
||||
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||
* It is generally better to create a new file per style scope.
|
||||
*
|
||||
*= require trix/dist/trix.css
|
||||
*= require_tree .
|
||||
*= require_self
|
||||
*/
|
@ -0,0 +1,4 @@
|
||||
/*
|
||||
Place all the styles related to the matching controller here.
|
||||
They will automatically be included in application.css.
|
||||
*/
|
80
actiontext/test/dummy/app/assets/stylesheets/scaffold.css
Normal file
80
actiontext/test/dummy/app/assets/stylesheets/scaffold.css
Normal file
@ -0,0 +1,80 @@
|
||||
body {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
margin: 33px;
|
||||
}
|
||||
|
||||
body, p, ol, ul, td {
|
||||
font-family: verdana, arial, helvetica, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #eee;
|
||||
padding: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
th {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0 5px 7px;
|
||||
}
|
||||
|
||||
div.field,
|
||||
div.actions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#notice {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.field_with_errors {
|
||||
padding: 2px;
|
||||
background-color: red;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#error_explanation {
|
||||
width: 450px;
|
||||
border: 2px solid red;
|
||||
padding: 7px 7px 0;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
#error_explanation h2 {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
padding: 5px 5px 5px 15px;
|
||||
font-size: 12px;
|
||||
margin: -7px -7px 0;
|
||||
background-color: #c00;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#error_explanation ul li {
|
||||
font-size: 12px;
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
@ -0,0 +1,4 @@
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
end
|
||||
end
|
@ -0,0 +1,2 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
end
|
0
actiontext/test/dummy/app/controllers/concerns/.keep
Normal file
0
actiontext/test/dummy/app/controllers/concerns/.keep
Normal file
58
actiontext/test/dummy/app/controllers/messages_controller.rb
Normal file
58
actiontext/test/dummy/app/controllers/messages_controller.rb
Normal file
@ -0,0 +1,58 @@
|
||||
class MessagesController < ApplicationController
|
||||
before_action :set_message, only: [:show, :edit, :update, :destroy]
|
||||
|
||||
# GET /messages
|
||||
def index
|
||||
@messages = Message.all
|
||||
end
|
||||
|
||||
# GET /messages/1
|
||||
def show
|
||||
end
|
||||
|
||||
# GET /messages/new
|
||||
def new
|
||||
@message = Message.new
|
||||
end
|
||||
|
||||
# GET /messages/1/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# POST /messages
|
||||
def create
|
||||
@message = Message.new(message_params)
|
||||
|
||||
if @message.save
|
||||
redirect_to @message, notice: 'Message was successfully created.'
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH/PUT /messages/1
|
||||
def update
|
||||
if @message.update(message_params)
|
||||
redirect_to @message, notice: 'Message was successfully updated.'
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /messages/1
|
||||
def destroy
|
||||
@message.destroy
|
||||
redirect_to messages_url, notice: 'Message was successfully destroyed.'
|
||||
end
|
||||
|
||||
private
|
||||
# Use callbacks to share common setup or constraints between actions.
|
||||
def set_message
|
||||
@message = Message.find(params[:id])
|
||||
end
|
||||
|
||||
# Only allow a trusted parameter "white list" through.
|
||||
def message_params
|
||||
params.require(:message).permit(:subject, :content)
|
||||
end
|
||||
end
|
2
actiontext/test/dummy/app/helpers/application_helper.rb
Normal file
2
actiontext/test/dummy/app/helpers/application_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module ApplicationHelper
|
||||
end
|
2
actiontext/test/dummy/app/helpers/messages_helper.rb
Normal file
2
actiontext/test/dummy/app/helpers/messages_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module MessagesHelper
|
||||
end
|
@ -0,0 +1 @@
|
||||
import "actiontext"
|
2
actiontext/test/dummy/app/jobs/application_job.rb
Normal file
2
actiontext/test/dummy/app/jobs/application_job.rb
Normal file
@ -0,0 +1,2 @@
|
||||
class ApplicationJob < ActiveJob::Base
|
||||
end
|
4
actiontext/test/dummy/app/mailers/application_mailer.rb
Normal file
4
actiontext/test/dummy/app/mailers/application_mailer.rb
Normal file
@ -0,0 +1,4 @@
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'from@example.com'
|
||||
layout 'mailer'
|
||||
end
|
3
actiontext/test/dummy/app/models/application_record.rb
Normal file
3
actiontext/test/dummy/app/models/application_record.rb
Normal file
@ -0,0 +1,3 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
0
actiontext/test/dummy/app/models/concerns/.keep
Normal file
0
actiontext/test/dummy/app/models/concerns/.keep
Normal file
4
actiontext/test/dummy/app/models/message.rb
Normal file
4
actiontext/test/dummy/app/models/message.rb
Normal file
@ -0,0 +1,4 @@
|
||||
class Message < ApplicationRecord
|
||||
has_rich_text :content
|
||||
has_rich_text :body
|
||||
end
|
7
actiontext/test/dummy/app/models/person.rb
Normal file
7
actiontext/test/dummy/app/models/person.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class Person < ApplicationRecord
|
||||
include ActionText::Attachable
|
||||
|
||||
def to_trix_content_attachment_partial_path
|
||||
"people/trix_content_attachment"
|
||||
end
|
||||
end
|
14
actiontext/test/dummy/app/views/layouts/application.html.erb
Normal file
14
actiontext/test/dummy/app/views/layouts/application.html.erb
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dummy</title>
|
||||
<%= csrf_meta_tags %>
|
||||
|
||||
<%= stylesheet_link_tag 'application', media: 'all' %>
|
||||
<%= javascript_pack_tag 'application' %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
13
actiontext/test/dummy/app/views/layouts/mailer.html.erb
Normal file
13
actiontext/test/dummy/app/views/layouts/mailer.html.erb
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<style>
|
||||
/* Email styles need to be inline */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
1
actiontext/test/dummy/app/views/layouts/mailer.text.erb
Normal file
1
actiontext/test/dummy/app/views/layouts/mailer.text.erb
Normal file
@ -0,0 +1 @@
|
||||
<%= yield %>
|
27
actiontext/test/dummy/app/views/messages/_form.html.erb
Normal file
27
actiontext/test/dummy/app/views/messages/_form.html.erb
Normal file
@ -0,0 +1,27 @@
|
||||
<%= form_with(model: message, local: true) do |form| %>
|
||||
<% if message.errors.any? %>
|
||||
<div id="error_explanation">
|
||||
<h2><%= pluralize(message.errors.count, "error") %> prohibited this message from being saved:</h2>
|
||||
|
||||
<ul>
|
||||
<% message.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="field">
|
||||
<%= form.label :subject %>
|
||||
<%= form.text_field :subject %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= form.label :content %>
|
||||
<%= form.rich_text_area :content, class: "trix-content" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= form.submit %>
|
||||
</div>
|
||||
<% end %>
|
6
actiontext/test/dummy/app/views/messages/edit.html.erb
Normal file
6
actiontext/test/dummy/app/views/messages/edit.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<h1>Editing Message</h1>
|
||||
|
||||
<%= render 'form', message: @message %>
|
||||
|
||||
<%= link_to 'Show', @message %> |
|
||||
<%= link_to 'Back', messages_path %>
|
29
actiontext/test/dummy/app/views/messages/index.html.erb
Normal file
29
actiontext/test/dummy/app/views/messages/index.html.erb
Normal file
@ -0,0 +1,29 @@
|
||||
<p id="notice"><%= notice %></p>
|
||||
|
||||
<h1>Messages</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Subject</th>
|
||||
<th>Content</th>
|
||||
<th colspan="3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% @messages.each do |message| %>
|
||||
<tr>
|
||||
<td><%= message.subject %></td>
|
||||
<td><%= message.content %></td>
|
||||
<td><%= link_to 'Show', message %></td>
|
||||
<td><%= link_to 'Edit', edit_message_path(message) %></td>
|
||||
<td><%= link_to 'Destroy', message, method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
<%= link_to 'New Message', new_message_path %>
|
5
actiontext/test/dummy/app/views/messages/new.html.erb
Normal file
5
actiontext/test/dummy/app/views/messages/new.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>New Message</h1>
|
||||
|
||||
<%= render 'form', message: @message %>
|
||||
|
||||
<%= link_to 'Back', messages_path %>
|
13
actiontext/test/dummy/app/views/messages/show.html.erb
Normal file
13
actiontext/test/dummy/app/views/messages/show.html.erb
Normal file
@ -0,0 +1,13 @@
|
||||
<p id="notice"><%= notice %></p>
|
||||
|
||||
<p>
|
||||
<strong>Subject:</strong>
|
||||
<%= @message.subject %>
|
||||
</p>
|
||||
|
||||
<div class="trix-content">
|
||||
<%= @message.content.html_safe %>
|
||||
</div>
|
||||
|
||||
<%= link_to 'Edit', edit_message_path(@message) %> |
|
||||
<%= link_to 'Back', messages_path %>
|
@ -0,0 +1,3 @@
|
||||
<span class="mentionable-person" gid="<%= person.to_gid %>">
|
||||
<%= person.name %>
|
||||
</span>
|
3
actiontext/test/dummy/bin/bundle
Executable file
3
actiontext/test/dummy/bin/bundle
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env ruby
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
||||
load Gem.bin_path('bundler', 'bundle')
|
4
actiontext/test/dummy/bin/rails
Executable file
4
actiontext/test/dummy/bin/rails
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env ruby
|
||||
APP_PATH = File.expand_path('../config/application', __dir__)
|
||||
require_relative '../config/boot'
|
||||
require 'rails/commands'
|
4
actiontext/test/dummy/bin/rake
Executable file
4
actiontext/test/dummy/bin/rake
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env ruby
|
||||
require_relative '../config/boot'
|
||||
require 'rake'
|
||||
Rake.application.run
|
36
actiontext/test/dummy/bin/setup
Executable file
36
actiontext/test/dummy/bin/setup
Executable file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'fileutils'
|
||||
include FileUtils
|
||||
|
||||
# path to your application root.
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
def system!(*args)
|
||||
system(*args) || abort("\n== Command #{args} failed ==")
|
||||
end
|
||||
|
||||
chdir APP_ROOT do
|
||||
# This script is a starting point to setup your application.
|
||||
# Add necessary setup steps to this file.
|
||||
|
||||
puts '== Installing dependencies =='
|
||||
system! 'gem install bundler --conservative'
|
||||
system('bundle check') || system!('bundle install')
|
||||
|
||||
# Install JavaScript dependencies if using Yarn
|
||||
# system('bin/yarn')
|
||||
|
||||
# puts "\n== Copying sample files =="
|
||||
# unless File.exist?('config/database.yml')
|
||||
# cp 'config/database.yml.sample', 'config/database.yml'
|
||||
# end
|
||||
|
||||
puts "\n== Preparing database =="
|
||||
system! 'bin/rails db:setup'
|
||||
|
||||
puts "\n== Removing old logs and tempfiles =="
|
||||
system! 'bin/rails log:clear tmp:clear'
|
||||
|
||||
puts "\n== Restarting application server =="
|
||||
system! 'bin/rails restart'
|
||||
end
|
31
actiontext/test/dummy/bin/update
Executable file
31
actiontext/test/dummy/bin/update
Executable file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'fileutils'
|
||||
include FileUtils
|
||||
|
||||
# path to your application root.
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
|
||||
def system!(*args)
|
||||
system(*args) || abort("\n== Command #{args} failed ==")
|
||||
end
|
||||
|
||||
chdir APP_ROOT do
|
||||
# This script is a way to update your development environment automatically.
|
||||
# Add necessary update steps to this file.
|
||||
|
||||
puts '== Installing dependencies =='
|
||||
system! 'gem install bundler --conservative'
|
||||
system('bundle check') || system!('bundle install')
|
||||
|
||||
# Install JavaScript dependencies if using Yarn
|
||||
# system('bin/yarn')
|
||||
|
||||
puts "\n== Updating database =="
|
||||
system! 'bin/rails db:migrate'
|
||||
|
||||
puts "\n== Removing old logs and tempfiles =="
|
||||
system! 'bin/rails log:clear tmp:clear'
|
||||
|
||||
puts "\n== Restarting application server =="
|
||||
system! 'bin/rails restart'
|
||||
end
|
11
actiontext/test/dummy/bin/yarn
Executable file
11
actiontext/test/dummy/bin/yarn
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env ruby
|
||||
APP_ROOT = File.expand_path('..', __dir__)
|
||||
Dir.chdir(APP_ROOT) do
|
||||
begin
|
||||
exec "yarnpkg", *ARGV
|
||||
rescue Errno::ENOENT
|
||||
$stderr.puts "Yarn executable was not detected in the system."
|
||||
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
|
||||
exit 1
|
||||
end
|
||||
end
|
5
actiontext/test/dummy/config.ru
Normal file
5
actiontext/test/dummy/config.ru
Normal file
@ -0,0 +1,5 @@
|
||||
# This file is used by Rack-based servers to start the application.
|
||||
|
||||
require_relative 'config/environment'
|
||||
|
||||
run Rails.application
|
19
actiontext/test/dummy/config/application.rb
Normal file
19
actiontext/test/dummy/config/application.rb
Normal file
@ -0,0 +1,19 @@
|
||||
require_relative 'boot'
|
||||
|
||||
require 'rails/all'
|
||||
|
||||
Bundler.require(*Rails.groups)
|
||||
require "action_text"
|
||||
|
||||
module Dummy
|
||||
class Application < Rails::Application
|
||||
# Initialize configuration defaults for originally generated Rails version.
|
||||
config.load_defaults 5.2
|
||||
|
||||
# Settings in config/environments/* take precedence over those specified here.
|
||||
# Application configuration can go into files in config/initializers
|
||||
# -- all .rb files in that directory are automatically loaded after loading
|
||||
# the framework and any gems in your application.
|
||||
end
|
||||
end
|
||||
|
5
actiontext/test/dummy/config/boot.rb
Normal file
5
actiontext/test/dummy/config/boot.rb
Normal file
@ -0,0 +1,5 @@
|
||||
# Set up gems listed in the Gemfile.
|
||||
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
|
||||
|
||||
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
||||
$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
|
10
actiontext/test/dummy/config/cable.yml
Normal file
10
actiontext/test/dummy/config/cable.yml
Normal file
@ -0,0 +1,10 @@
|
||||
development:
|
||||
adapter: async
|
||||
|
||||
test:
|
||||
adapter: async
|
||||
|
||||
production:
|
||||
adapter: redis
|
||||
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
||||
channel_prefix: dummy_production
|
25
actiontext/test/dummy/config/database.yml
Normal file
25
actiontext/test/dummy/config/database.yml
Normal file
@ -0,0 +1,25 @@
|
||||
# SQLite version 3.x
|
||||
# gem install sqlite3
|
||||
#
|
||||
# Ensure the SQLite 3 gem is defined in your Gemfile
|
||||
# gem 'sqlite3'
|
||||
#
|
||||
default: &default
|
||||
adapter: sqlite3
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
timeout: 5000
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
database: db/development.sqlite3
|
||||
|
||||
# Warning: The database defined as "test" will be erased and
|
||||
# re-generated from your development database when you run "rake".
|
||||
# Do not set this db to the same as development or production.
|
||||
test:
|
||||
<<: *default
|
||||
database: db/test.sqlite3
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
database: db/production.sqlite3
|
5
actiontext/test/dummy/config/environment.rb
Normal file
5
actiontext/test/dummy/config/environment.rb
Normal file
@ -0,0 +1,5 @@
|
||||
# Load the Rails application.
|
||||
require_relative 'application'
|
||||
|
||||
# Initialize the Rails application.
|
||||
Rails.application.initialize!
|
63
actiontext/test/dummy/config/environments/development.rb
Normal file
63
actiontext/test/dummy/config/environments/development.rb
Normal file
@ -0,0 +1,63 @@
|
||||
Rails.application.configure do
|
||||
# Verifies that versions and hashed value of the package contents in the project's package.json
|
||||
# config.webpacker.check_yarn_integrity = true
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
||||
# In the development environment your application's code is reloaded on
|
||||
# every request. This slows down response time but is perfect for development
|
||||
# since you don't have to restart the web server when you make code changes.
|
||||
config.cache_classes = false
|
||||
|
||||
# Do not eager load code on boot.
|
||||
config.eager_load = false
|
||||
|
||||
# Show full error reports.
|
||||
config.consider_all_requests_local = true
|
||||
|
||||
# Enable/disable caching. By default caching is disabled.
|
||||
# Run rails dev:cache to toggle caching.
|
||||
if Rails.root.join('tmp', 'caching-dev.txt').exist?
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
config.cache_store = :memory_store
|
||||
config.public_file_server.headers = {
|
||||
'Cache-Control' => "public, max-age=#{2.days.to_i}"
|
||||
}
|
||||
else
|
||||
config.action_controller.perform_caching = false
|
||||
|
||||
config.cache_store = :null_store
|
||||
end
|
||||
|
||||
# Store uploaded files on the local file system (see config/storage.yml for options)
|
||||
config.active_storage.service = :local
|
||||
|
||||
# Don't care if the mailer can't send.
|
||||
config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# Print deprecation notices to the Rails logger.
|
||||
config.active_support.deprecation = :log
|
||||
|
||||
# Raise an error on page load if there are pending migrations.
|
||||
config.active_record.migration_error = :page_load
|
||||
|
||||
# Highlight code that triggered database queries in logs.
|
||||
config.active_record.verbose_query_logs = true
|
||||
|
||||
# Debug mode disables concatenation and preprocessing of assets.
|
||||
# This option may cause significant delays in view rendering with a large
|
||||
# number of complex assets.
|
||||
config.assets.debug = true
|
||||
|
||||
# Suppress logger output for asset requests.
|
||||
config.assets.quiet = true
|
||||
|
||||
# Raises error for missing translations
|
||||
# config.action_view.raise_on_missing_translations = true
|
||||
|
||||
# Use an evented file watcher to asynchronously detect changes in source code,
|
||||
# routes, locales, etc. This feature depends on the listen gem.
|
||||
# config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
||||
end
|
96
actiontext/test/dummy/config/environments/production.rb
Normal file
96
actiontext/test/dummy/config/environments/production.rb
Normal file
@ -0,0 +1,96 @@
|
||||
Rails.application.configure do
|
||||
# Verifies that versions and hashed value of the package contents in the project's package.json
|
||||
config.webpacker.check_yarn_integrity = false
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
||||
# Code is not reloaded between requests.
|
||||
config.cache_classes = true
|
||||
|
||||
# Eager load code on boot. This eager loads most of Rails and
|
||||
# your application in memory, allowing both threaded web servers
|
||||
# and those relying on copy on write to perform better.
|
||||
# Rake tasks automatically ignore this option for performance.
|
||||
config.eager_load = true
|
||||
|
||||
# Full error reports are disabled and caching is turned on.
|
||||
config.consider_all_requests_local = false
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
||||
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
|
||||
# config.require_master_key = true
|
||||
|
||||
# Disable serving static files from the `/public` folder by default since
|
||||
# Apache or NGINX already handles this.
|
||||
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
||||
|
||||
# Compress JavaScripts and CSS.
|
||||
config.assets.js_compressor = :uglifier
|
||||
# config.assets.css_compressor = :sass
|
||||
|
||||
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||
config.assets.compile = false
|
||||
|
||||
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
|
||||
|
||||
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
||||
# config.action_controller.asset_host = 'http://assets.example.com'
|
||||
|
||||
# Specifies the header that your server uses for sending files.
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
|
||||
|
||||
# Store uploaded files on the local file system (see config/storage.yml for options)
|
||||
config.active_storage.service = :local
|
||||
|
||||
# Mount Action Cable outside main process or domain
|
||||
# config.action_cable.mount_path = nil
|
||||
# config.action_cable.url = 'wss://example.com/cable'
|
||||
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
|
||||
|
||||
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
||||
# config.force_ssl = true
|
||||
|
||||
# Use the lowest log level to ensure availability of diagnostic information
|
||||
# when problems arise.
|
||||
config.log_level = :debug
|
||||
|
||||
# Prepend all log lines with the following tags.
|
||||
config.log_tags = [ :request_id ]
|
||||
|
||||
# Use a different cache store in production.
|
||||
# config.cache_store = :mem_cache_store
|
||||
|
||||
# Use a real queuing backend for Active Job (and separate queues per environment)
|
||||
# config.active_job.queue_adapter = :resque
|
||||
# config.active_job.queue_name_prefix = "dummy_#{Rails.env}"
|
||||
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# Ignore bad email addresses and do not raise email delivery errors.
|
||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||
# config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation cannot be found).
|
||||
config.i18n.fallbacks = true
|
||||
|
||||
# Send deprecation notices to registered listeners.
|
||||
config.active_support.deprecation = :notify
|
||||
|
||||
# Use default logging formatter so that PID and timestamp are not suppressed.
|
||||
config.log_formatter = ::Logger::Formatter.new
|
||||
|
||||
# Use a different logger for distributed setups.
|
||||
# require 'syslog/logger'
|
||||
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
|
||||
|
||||
if ENV["RAILS_LOG_TO_STDOUT"].present?
|
||||
logger = ActiveSupport::Logger.new(STDOUT)
|
||||
logger.formatter = config.log_formatter
|
||||
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
||||
end
|
||||
|
||||
# Do not dump schema after migrations.
|
||||
config.active_record.dump_schema_after_migration = false
|
||||
end
|
46
actiontext/test/dummy/config/environments/test.rb
Normal file
46
actiontext/test/dummy/config/environments/test.rb
Normal file
@ -0,0 +1,46 @@
|
||||
Rails.application.configure do
|
||||
# Settings specified here will take precedence over those in config/application.rb.
|
||||
|
||||
# The test environment is used exclusively to run your application's
|
||||
# test suite. You never need to work with it otherwise. Remember that
|
||||
# your test database is "scratch space" for the test suite and is wiped
|
||||
# and recreated between test runs. Don't rely on the data there!
|
||||
config.cache_classes = true
|
||||
|
||||
# Do not eager load code on boot. This avoids loading your whole application
|
||||
# just for the purpose of running a single test. If you are using a tool that
|
||||
# preloads Rails for running tests, you may have to set it to true.
|
||||
config.eager_load = false
|
||||
|
||||
# Configure public file server for tests with Cache-Control for performance.
|
||||
config.public_file_server.enabled = true
|
||||
config.public_file_server.headers = {
|
||||
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
|
||||
}
|
||||
|
||||
# Show full error reports and disable caching.
|
||||
config.consider_all_requests_local = true
|
||||
config.action_controller.perform_caching = false
|
||||
|
||||
# Raise exceptions instead of rendering exception templates.
|
||||
config.action_dispatch.show_exceptions = false
|
||||
|
||||
# Disable request forgery protection in test environment.
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
|
||||
# Store uploaded files on the local file system in a temporary directory
|
||||
config.active_storage.service = :test
|
||||
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
# Tell Action Mailer not to deliver emails to the real world.
|
||||
# The :test delivery method accumulates sent emails in the
|
||||
# ActionMailer::Base.deliveries array.
|
||||
config.action_mailer.delivery_method = :test
|
||||
|
||||
# Print deprecation notices to the stderr.
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
# Raises error for missing translations
|
||||
# config.action_view.raise_on_missing_translations = true
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# ActiveSupport::Reloader.to_prepare do
|
||||
# ApplicationController.renderer.defaults.merge!(
|
||||
# http_host: 'example.org',
|
||||
# https: false
|
||||
# )
|
||||
# end
|
14
actiontext/test/dummy/config/initializers/assets.rb
Normal file
14
actiontext/test/dummy/config/initializers/assets.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Version of your assets, change this if you want to expire all your assets.
|
||||
Rails.application.config.assets.version = '1.0'
|
||||
|
||||
# Add additional assets to the asset load path.
|
||||
# Rails.application.config.assets.paths << Emoji.images_path
|
||||
# Add Yarn node_modules folder to the asset load path.
|
||||
Rails.application.config.assets.paths << Rails.root.join('node_modules')
|
||||
|
||||
# Precompile additional assets.
|
||||
# application.js, application.css, and all non-JS/CSS in the app/assets
|
||||
# folder are already added.
|
||||
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
|
@ -0,0 +1,7 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
|
||||
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
|
||||
|
||||
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
|
||||
# Rails.backtrace_cleaner.remove_silencers!
|
@ -0,0 +1,22 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Define an application-wide content security policy
|
||||
# For further information see the following documentation
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||
|
||||
# Rails.application.config.content_security_policy do |policy|
|
||||
# policy.default_src :self, :https
|
||||
# policy.font_src :self, :https, :data
|
||||
# policy.img_src :self, :https, :data
|
||||
# policy.object_src :none
|
||||
# policy.script_src :self, :https
|
||||
# policy.style_src :self, :https, :unsafe_inline
|
||||
|
||||
# # Specify URI for violation reports
|
||||
# # policy.report_uri "/csp-violation-report-endpoint"
|
||||
# end
|
||||
|
||||
# Report CSP violations to a specified URI
|
||||
# For further information see the following documentation:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
||||
# Rails.application.config.content_security_policy_report_only = true
|
@ -0,0 +1,5 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Specify a serializer for the signed and encrypted cookie jars.
|
||||
# Valid options are :json, :marshal, and :hybrid.
|
||||
Rails.application.config.action_dispatch.cookies_serializer = :json
|
@ -0,0 +1,4 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Configure sensitive parameters which will be filtered from the log file.
|
||||
Rails.application.config.filter_parameters += [:password]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user