aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2018-06-27 16:20:58 +0200
committerGitHub <noreply@github.com>2018-06-27 16:20:58 +0200
commitb430ba4754f59934acb4806c83d10dee5af8de9e (patch)
tree1d7482d313f05a800f63a3a490293c9593b2a6a2
parenta4af9580d604221ec36d2cd5671eb7f256ae5173 (diff)
parent60050ee41275eda25601beaa453e26841511d492 (diff)
downloadrails-b430ba4754f59934acb4806c83d10dee5af8de9e.tar.gz
rails-b430ba4754f59934acb4806c83d10dee5af8de9e.tar.bz2
rails-b430ba4754f59934acb4806c83d10dee5af8de9e.zip
Merge pull request #1 from basecamp/separate-rich-text-record
Separate Rich Text record
-rw-r--r--Gemfile.lock4
-rw-r--r--README.md20
-rw-r--r--app/helpers/action_text/tag_helper.rb14
-rw-r--r--app/models/action_text/rich_text.rb17
-rw-r--r--app/views/action_text/content/_layout.html.erb3
-rw-r--r--db/migrate/201805281641_create_action_text_tables.rb14
-rw-r--r--lib/action_text/attribute.rb40
-rw-r--r--lib/action_text/content.rb6
-rw-r--r--lib/tasks/actiontext.rake28
-rw-r--r--lib/templates/actiontext.css7
-rw-r--r--package.json8
-rw-r--r--test/dummy/app/views/messages/_form.html.erb2
-rw-r--r--test/dummy/config/environments/development.rb2
-rw-r--r--test/dummy/db/migrate/20180208205311_create_messages.rb2
-rw-r--r--test/dummy/db/migrate/2018052816_create_action_text_tables.rb14
-rw-r--r--test/dummy/db/schema.rb11
-rw-r--r--test/unit/content_test.rb4
-rw-r--r--test/unit/model_test.rb10
18 files changed, 173 insertions, 33 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 116635c788..d40eabf684 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -71,7 +71,7 @@ GEM
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.11.3)
- nio4r (2.3.0)
+ nio4r (2.3.1)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
rack (2.0.4)
@@ -135,4 +135,4 @@ DEPENDENCIES
webpacker (~> 3.2.2)
BUNDLED WITH
- 1.16.1
+ 1.16.2
diff --git a/README.md b/README.md
index 715c60e9c2..72eb6d44cf 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,8 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed.
```ruby
# Gemfile
- gem "activetext", github: "basecamp/activetext", require: "action_text"
- gem "mini_magick" # for Active Storage variants
+ gem "actiontext", github: "basecamp/actiontext", require: "action_text"
+ gem "image_processing", "~> 1.2" # for Active Storage variants
```
1. Install the npm package:
@@ -19,7 +19,7 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed.
```js
// package.json
"dependencies": {
- "activetext": "basecamp/activetext"
+ "actiontext": "basecamp/actiontext"
}
```
@@ -29,9 +29,17 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed.
```js
// app/javascript/packs/application.js
- import "activetext"
+ import "actiontext"
```
+1. Migrate the database
+
+ ```
+ ./bin/rails active_storage:install
+ ./bin/rails action_text:install
+ ./bin/rails db:migrate
+ ```
+
1. Declare text columns as Action Text attributes:
```ruby
@@ -41,7 +49,7 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed.
end
```
-1. Replace form `text_area`s with `rich_text_field`s:
+1. Replace form `text_area`s with `rich_text_area`s:
```erb
<%# app/views/messages/_form.html.erb %>
@@ -49,7 +57,7 @@ Assumes a Rails 5.2+ application with Active Storage and Webpacker installed.
<div class="field">
<%= form.label :content %>
- <%= form.rich_text_field :content %>
+ <%= form.rich_text_area :content %>
</div>
<% end %>
diff --git a/app/helpers/action_text/tag_helper.rb b/app/helpers/action_text/tag_helper.rb
index bd2727bca6..0033538773 100644
--- a/app/helpers/action_text/tag_helper.rb
+++ b/app/helpers/action_text/tag_helper.rb
@@ -2,10 +2,12 @@ module ActionText
module TagHelper
cattr_accessor(:id, instance_accessor: false) { 0 }
- def rich_text_field_tag(name, value = nil, options = {})
+ 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] = rails_direct_uploads_url
options[:data][:blob_url_template] = rails_service_blob_url(":signed_id", ":filename")
@@ -26,23 +28,23 @@ module ActionView::Helpers
options = @options.stringify_keys
add_default_name_and_id(options)
options["input"] ||= dom_id(object, [options["id"], :trix_input].compact.join("_"))
- @template_object.rich_text_field_tag(options.delete("name"), editable_value, options)
+ @template_object.rich_text_area_tag(options.delete("name"), editable_value, options)
end
def editable_value
- value.try(:to_trix_html)
+ value.body.try(:to_trix_html)
end
end
module FormHelper
- def rich_text_field(object_name, method, options = {})
+ def rich_text_area(object_name, method, options = {})
Tags::ActionText.new(object_name, method, self, options).render
end
end
class FormBuilder
- def rich_text_field(method, options = {})
- @template.rich_text_field(@object_name, method, objectify_options(options))
+ def rich_text_area(method, options = {})
+ @template.rich_text_area(@object_name, method, objectify_options(options))
end
end
end
diff --git a/app/models/action_text/rich_text.rb b/app/models/action_text/rich_text.rb
new file mode 100644
index 0000000000..6747def9a5
--- /dev/null
+++ b/app/models/action_text/rich_text.rb
@@ -0,0 +1,17 @@
+class ActionText::RichText < ActiveRecord::Base
+ self.table_name = "action_text_rich_texts"
+
+ serialize :body, ActionText::Content
+
+ belongs_to :record, polymorphic: true, touch: true
+ has_many_attached :embeds
+
+ after_save do
+ self.embeds_blobs = body.attachments.map(&:attachable)
+ end
+
+
+ def to_s
+ body.to_s.html_safe
+ end
+end
diff --git a/app/views/action_text/content/_layout.html.erb b/app/views/action_text/content/_layout.html.erb
new file mode 100644
index 0000000000..c0b86a189e
--- /dev/null
+++ b/app/views/action_text/content/_layout.html.erb
@@ -0,0 +1,3 @@
+<div class="trix-content">
+ <%=raw document %>
+</div>
diff --git a/db/migrate/201805281641_create_action_text_tables.rb b/db/migrate/201805281641_create_action_text_tables.rb
new file mode 100644
index 0000000000..872987749a
--- /dev/null
+++ b/db/migrate/201805281641_create_action_text_tables.rb
@@ -0,0 +1,14 @@
+class CreateActionTextTables < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_text_rich_texts do |t|
+ t.string :name, null: false
+ t.text :body, limit: 16777215, null: false
+ 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
diff --git a/lib/action_text/attribute.rb b/lib/action_text/attribute.rb
index 32dd1d0e3f..299c26b29f 100644
--- a/lib/action_text/attribute.rb
+++ b/lib/action_text/attribute.rb
@@ -3,15 +3,41 @@ module ActionText
extend ActiveSupport::Concern
class_methods do
- def has_rich_text(attribute_name)
- serialize(attribute_name, ActionText::Content)
+ # 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.body.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_emebds # 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
- has_many_attached "#{attribute_name}_attachments"
+ def #{name}=(body)
+ #{name}.body = body
+ end
+ CODE
- after_save do
- blobs = public_send(attribute_name).attachments.map(&:attachable)
- public_send("#{attribute_name}_attachments_blobs=", blobs)
- end
+ 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 { public_send(name).save if public_send(name).changed? }
end
end
end
diff --git a/lib/action_text/content.rb b/lib/action_text/content.rb
index c63e0c3525..c296896ac4 100644
--- a/lib/action_text/content.rb
+++ b/lib/action_text/content.rb
@@ -53,8 +53,12 @@ module ActionText
end.to_html
end
+ def to_html_with_layout
+ ActionText.renderer.render(partial: "action_text/content/layout", locals: { document: to_html })
+ end
+
def to_s
- to_html.html_safe
+ to_html_with_layout
end
def as_json(*)
diff --git a/lib/tasks/actiontext.rake b/lib/tasks/actiontext.rake
new file mode 100644
index 0000000000..817a4c67f0
--- /dev/null
+++ b/lib/tasks/actiontext.rake
@@ -0,0 +1,28 @@
+# 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: %i( environment copy_migration copy_stylesheet )
+
+ task :copy_migration do
+ if Rake::Task.task_defined?("action_text:install:migrations")
+ Rake::Task["action_text:install:migrations"].invoke
+ else
+ Rake::Task["app:action_text:install:migrations"].invoke
+ end
+ end
+
+ STYLESHEET_TEMPLATE_PATH = File.expand_path("../templates/actiontext.css", __dir__)
+ STYLESHEET_APP_PATH = Rails.root.join("app/assets/stylesheets/actiontext.css")
+
+ task :copy_stylesheet do
+ if File.exist?(STYLESHEET_APP_PATH)
+ puts "Won't copy Action Text stylesheet as it already exists"
+ else
+ FileUtils.cp STYLESHEET_TEMPLATE_PATH, STYLESHEET_APP_PATH
+ end
+ end
+end
diff --git a/lib/templates/actiontext.css b/lib/templates/actiontext.css
new file mode 100644
index 0000000000..97c083c24b
--- /dev/null
+++ b/lib/templates/actiontext.css
@@ -0,0 +1,7 @@
+/*
+ * 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
+*/
diff --git a/package.json b/package.json
index 0f11cee9b5..ef4481fbfd 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
{
- "name": "activetext",
+ "name": "actiontext",
"version": "0.1.0",
"description": "Edit and display rich text in Rails applications",
- "main": "app/javascript/activetext/index.js",
+ "main": "app/javascript/actiontext/index.js",
"files": [
- "app/javascript/activetext/*.js"
+ "app/javascript/actiontext/*.js"
],
- "repository": "https://github.com/basecamp/activetext",
+ "repository": "https://github.com/basecamp/actiontext",
"author": "Basecamp, LLC",
"contributors": [
"Javan Makhmali <javan@javan.us>",
diff --git a/test/dummy/app/views/messages/_form.html.erb b/test/dummy/app/views/messages/_form.html.erb
index bd5f0bea4a..3b8a174884 100644
--- a/test/dummy/app/views/messages/_form.html.erb
+++ b/test/dummy/app/views/messages/_form.html.erb
@@ -18,7 +18,7 @@
<div class="field">
<%= form.label :content %>
- <%= form.rich_text_field :content, class: "trix-content" %>
+ <%= form.rich_text_area :content, class: "trix-content" %>
</div>
<div class="actions">
diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb
index 42768bf68c..f09b9a00c4 100644
--- a/test/dummy/config/environments/development.rb
+++ b/test/dummy/config/environments/development.rb
@@ -1,6 +1,6 @@
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
+ # 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
diff --git a/test/dummy/db/migrate/20180208205311_create_messages.rb b/test/dummy/db/migrate/20180208205311_create_messages.rb
index 6817d5933f..8e43559672 100644
--- a/test/dummy/db/migrate/20180208205311_create_messages.rb
+++ b/test/dummy/db/migrate/20180208205311_create_messages.rb
@@ -2,8 +2,6 @@ class CreateMessages < ActiveRecord::Migration[5.2]
def change
create_table :messages do |t|
t.string :subject
- t.text :content
-
t.timestamps
end
end
diff --git a/test/dummy/db/migrate/2018052816_create_action_text_tables.rb b/test/dummy/db/migrate/2018052816_create_action_text_tables.rb
new file mode 100644
index 0000000000..872987749a
--- /dev/null
+++ b/test/dummy/db/migrate/2018052816_create_action_text_tables.rb
@@ -0,0 +1,14 @@
+class CreateActionTextTables < ActiveRecord::Migration[5.2]
+ def change
+ create_table :action_text_rich_texts do |t|
+ t.string :name, null: false
+ t.text :body, limit: 16777215, null: false
+ 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
diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb
index f892368183..30bf844e9c 100644
--- a/test/dummy/db/schema.rb
+++ b/test/dummy/db/schema.rb
@@ -12,6 +12,16 @@
ActiveRecord::Schema.define(version: 2018_02_12_164506) do
+ create_table "action_text_rich_texts", force: :cascade do |t|
+ t.string "name", null: false
+ t.text "body", limit: 16777215, null: false
+ t.string "record_type", null: false
+ t.integer "record_id", null: 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
+
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@@ -35,7 +45,6 @@ ActiveRecord::Schema.define(version: 2018_02_12_164506) do
create_table "messages", force: :cascade do |t|
t.string "subject"
- t.text "content"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
diff --git a/test/unit/content_test.rb b/test/unit/content_test.rb
index e5773361e4..b1a06e2cfa 100644
--- a/test/unit/content_test.rb
+++ b/test/unit/content_test.rb
@@ -3,8 +3,8 @@ require_relative '../test_helper'
module ActionText
class ContentTest < ActiveSupport::TestCase
test "plain text conversion" do
- message = Message.create!(subject: "Greetings", content: "<h1>Hello world</h1>")
- assert_equal "Hello world", message.content.to_plain_text
+ message = Message.new(subject: "Greetings", content: "<h1>Hello world</h1>")
+ assert_equal "Hello world", message.content.body.to_plain_text
end
end
end
diff --git a/test/unit/model_test.rb b/test/unit/model_test.rb
new file mode 100644
index 0000000000..b7c5333c4d
--- /dev/null
+++ b/test/unit/model_test.rb
@@ -0,0 +1,10 @@
+require_relative '../test_helper'
+
+module ActionText
+ class ModelTest < ActiveSupport::TestCase
+ test "saving content" do
+ message = Message.create!(subject: "Greetings", content: "<h1>Hello world</h1>")
+ assert_equal "Hello world", message.content.body.to_plain_text
+ end
+ end
+end