diff --git a/app/frontend/js/components/Edit/TrixField.vue b/app/frontend/js/components/Edit/TrixField.vue
new file mode 100644
index 0000000000..c46dab9a43
--- /dev/null
+++ b/app/frontend/js/components/Edit/TrixField.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/frontend/js/components/Show/TrixField.vue b/app/frontend/js/components/Show/TrixField.vue
new file mode 100644
index 0000000000..9ce7663841
--- /dev/null
+++ b/app/frontend/js/components/Show/TrixField.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/frontend/js/components/index.js b/app/frontend/js/components/index.js
index 9a6f9e995a..6026e54486 100644
--- a/app/frontend/js/components/index.js
+++ b/app/frontend/js/components/index.js
@@ -48,6 +48,7 @@ Vue.component('show-country-field', require('@/js/components/Sh
Vue.component('show-badge-field', require('@/js/components/Show/BadgeField.vue').default)
Vue.component('show-heading-field', require('@/js/components/Show/HeadingField.vue').default)
Vue.component('show-code-field', require('@/js/components/Show/CodeField.vue').default)
+Vue.component('show-trix-field', require('@/js/components/Show/TrixField.vue').default)
Vue.component('edit-field-wrapper', require('@/js/components/Edit/FieldWrapper.vue').default)
Vue.component('edit-id-field', require('@/js/components/Edit/IdField.vue').default)
@@ -70,6 +71,7 @@ Vue.component('edit-country-field', require('@/js/components/Ed
Vue.component('edit-heading-field', require('@/js/components/Show/HeadingField.vue').default)
Vue.component('edit-code-field', require('@/js/components/Edit/CodeField.vue').default)
Vue.component('edit-hidden-field', require('@/js/components/Edit/HiddenField.vue').default)
+Vue.component('edit-trix-field', require('@/js/components/Edit/TrixField.vue').default)
// Form Fields
Vue.component('input-component', require('@/js/components/InputComponent.vue').default)
diff --git a/lib/avo/app/fields/trix_field.rb b/lib/avo/app/fields/trix_field.rb
new file mode 100644
index 0000000000..660799538a
--- /dev/null
+++ b/lib/avo/app/fields/trix_field.rb
@@ -0,0 +1,25 @@
+require_relative 'field'
+
+module Avo
+ module Fields
+ class TrixField < Field
+ def initialize(name, **args, &block)
+ @defaults = {
+ component: 'trix-field',
+ }
+
+ super(name, **args, &block)
+
+ hide_on :index
+
+ @always_show = args[:always_show].present? ? args[:always_show] : false
+ end
+
+ def hydrate_field(fields, model, resource, view)
+ {
+ always_show: @always_show
+ }
+ end
+ end
+ end
+end
diff --git a/lib/avo/app/resource_fields.rb b/lib/avo/app/resource_fields.rb
index e40be35990..5ac1ed53d3 100644
--- a/lib/avo/app/resource_fields.rb
+++ b/lib/avo/app/resource_fields.rb
@@ -16,6 +16,10 @@ def get_fields
def add_field(resource, field)
@@fields[resource].push field
end
+
+ def trix(name, **args, &block)
+ @@fields[self].push Avo::Fields::TrixField::new(name, **args, &block)
+ end
end
end
end
diff --git a/package.json b/package.json
index aa73a5cb05..d36759da51 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"dependencies": {
"@rails/activestorage": "^6.0.2-1",
"@rails/webpacker": "^4.2.2",
+ "@tailwindcss/typography": "^0.2.0",
"autoprefixer": "^9.7.4",
"axios": "^0.19.2",
"css-loader": "^3.5.3",
@@ -58,6 +59,7 @@
"vue-svg-loader": "^0.16.0",
"vue-template-compiler": "^2.6.11",
"vue-toasted": "^1.1.28",
+ "vue-trix": "^1.1.11",
"vue-turbolinks": "^2.1.0",
"vuejs-paginate": "^2.1.0",
"vuex": "^3.5.1",
diff --git a/spec/dummy/app/avo/resources/post.rb b/spec/dummy/app/avo/resources/post.rb
index 3cfe06ea9a..b4ecb5df5a 100644
--- a/spec/dummy/app/avo/resources/post.rb
+++ b/spec/dummy/app/avo/resources/post.rb
@@ -11,13 +11,14 @@ def initialize
fields do
id
text :name, required: true
- textarea :body, nullable: true, null_values: ['0', '', 'null', 'nil'], format_using: -> (value) { value.to_s.truncate 100 }
+ trix :body, placeholder: 'Enter text', always_show: false
file :cover_photo, is_image: true
boolean :is_featured
boolean :is_published do |model|
model.published_at.present?
end
+
belongs_to :user, searchable: false, placeholder: '—'
end
diff --git a/spec/dummy/app/avo/resources/team.rb b/spec/dummy/app/avo/resources/team.rb
index 5ee8042cbf..80d00d8062 100644
--- a/spec/dummy/app/avo/resources/team.rb
+++ b/spec/dummy/app/avo/resources/team.rb
@@ -9,7 +9,7 @@ def initialize
fields do
id
text :name
- textarea :description, rows: 5, readonly: false, hide_on: :index, format_using: -> (value) { value.to_s.truncate 30 }, required: true, default: 'This team is wonderful!'
+ textarea :description, rows: 5, readonly: false, hide_on: :index, format_using: -> (value) { value.to_s.truncate 30 }, default: 'This team is wonderful!', nullable: true, null_values: ['0', '', 'null', 'nil']
number :members_count do |model|
model.members.count
diff --git a/spec/system/avo/nullable_field_spec.rb b/spec/system/avo/nullable_field_spec.rb
index 750146c3f8..eadbb10e46 100644
--- a/spec/system/avo/nullable_field_spec.rb
+++ b/spec/system/avo/nullable_field_spec.rb
@@ -2,76 +2,76 @@
RSpec.describe 'NullableField', type: :system do
describe 'without input (specifying null_values: ["", "0", "null", "nil"])' do
- let!(:post) { create :post, body: nil }
+ let!(:team) { create :team, description: nil }
context 'show' do
- it 'displays the posts empty body (dash)' do
- visit "/avo/resources/post/#{post.id}"
+ it 'displays the teams empty description (dash)' do
+ visit "/avo/resources/teams/#{team.id}"
- expect(find_field_value_element('body')).to have_text empty_dash
+ expect(find_field_value_element('description')).to have_text empty_dash
end
end
context 'edit' do
- it 'has the posts body empty' do
- visit "/avo/resources/posts/#{post.id}/edit"
+ it 'has the teams description empty' do
+ visit "/avo/resources/teams/#{team.id}/edit"
- expect(find_field('body').value).to eq ''
+ expect(find_field('description').value).to eq ''
end
end
end
describe 'with regular input (specifying null_values: ["", "0", "null", "nil"])' do
- let!(:post) { create :post, body: 'descr' }
+ let!(:team) { create :team, description: 'descr' }
context 'edit' do
- it 'has the posts body prefilled' do
- visit "/avo/resources/posts/#{post.id}/edit"
+ it 'has the teams description prefilled' do
+ visit "/avo/resources/teams/#{team.id}/edit"
- expect(find_field('body').value).to eq 'descr'
+ expect(find_field('description').value).to eq 'descr'
end
- it 'changes the posts body to null ("" - empty string)' do
- visit "/avo/resources/posts/#{post.id}/edit"
+ it 'changes the teams description to null ("" - empty string)' do
+ visit "/avo/resources/teams/#{team.id}/edit"
- fill_in 'body', with: ''
+ fill_in 'description', with: ''
click_on 'Save'
wait_for_loaded
- expect(current_path).to eql "/avo/resources/posts/#{post.id}"
- expect(find_field_value_element('body')).to have_text empty_dash
+ expect(current_path).to eql "/avo/resources/teams/#{team.id}"
+ expect(find_field_value_element('description')).to have_text empty_dash
end
- it 'changes the posts body to null ("0")' do
- visit "/avo/resources/posts/#{post.id}/edit"
+ it 'changes the teams description to null ("0")' do
+ visit "/avo/resources/teams/#{team.id}/edit"
- fill_in 'body', with: '0'
+ fill_in 'description', with: '0'
click_on 'Save'
wait_for_loaded
- expect(current_path).to eql "/avo/resources/posts/#{post.id}"
- expect(find_field_value_element('body')).to have_text empty_dash
+ expect(current_path).to eql "/avo/resources/teams/#{team.id}"
+ expect(find_field_value_element('description')).to have_text empty_dash
end
- it 'changes the posts body to null ("nil")' do
- visit "/avo/resources/posts/#{post.id}/edit"
+ it 'changes the teams description to null ("nil")' do
+ visit "/avo/resources/teams/#{team.id}/edit"
- fill_in 'body', with: 'nil'
+ fill_in 'description', with: 'nil'
click_on 'Save'
wait_for_loaded
- expect(current_path).to eql "/avo/resources/posts/#{post.id}"
- expect(find_field_value_element('body')).to have_text empty_dash
+ expect(current_path).to eql "/avo/resources/teams/#{team.id}"
+ expect(find_field_value_element('description')).to have_text empty_dash
end
- it 'changes the postss body to null ("null")' do
- visit "/avo/resources/posts/#{post.id}/edit"
+ it 'changes the teams description to null ("null")' do
+ visit "/avo/resources/teams/#{team.id}/edit"
- fill_in 'body', with: 'null'
+ fill_in 'description', with: 'null'
click_on 'Save'
wait_for_loaded
- expect(current_path).to eql "/avo/resources/posts/#{post.id}"
- expect(find_field_value_element('body')).to have_text empty_dash
+ expect(current_path).to eql "/avo/resources/teams/#{team.id}"
+ expect(find_field_value_element('description')).to have_text empty_dash
end
end
end
diff --git a/spec/system/avo/trix_field_spec.rb b/spec/system/avo/trix_field_spec.rb
new file mode 100644
index 0000000000..40335c4f13
--- /dev/null
+++ b/spec/system/avo/trix_field_spec.rb
@@ -0,0 +1,93 @@
+require 'rails_helper'
+
+RSpec.describe 'TrixField', type: :system do
+ describe 'without value' do
+ let!(:post) { create :post , body: '' }
+
+ context 'show' do
+ it 'displays the posts empty body (dash)' do
+ visit "/avo/resources/posts/#{post.id}"
+
+ expect(find_field_element('body')).to have_text empty_dash
+ end
+ end
+
+ context 'edit' do
+ it 'has the posts body label and empty trix editor and placeholder' do
+ visit "/avo/resources/posts/#{post.id}/edit"
+
+ body_element = find_field_element('body')
+
+ expect(body_element).to have_text 'Body'
+
+ expect(find(:xpath, "//trix-editor[@input='trixEditor']")[:placeholder]).to have_text('Enter text')
+ expect(find_trix_editor('trixEditor')).to have_text('')
+ end
+
+ it 'change the posts body text' do
+ visit "/avo/resources/posts/#{post.id}/edit"
+
+ fill_in_trix_editor 'trixEditor', with: 'Works for us!!!'
+
+ click_on 'Save'
+ wait_for_loaded
+
+ click_on 'Show Content'
+
+ expect(find_field_value_element('body')).to have_text 'Works for us!!!'
+ end
+ end
+ end
+
+ describe 'with regular value' do
+ let!(:body) { 'Example trix text.' }
+ let!(:post) { create :post , body: body }
+
+ context 'show' do
+ it 'displays the posts body' do
+ visit "/avo/resources/posts/#{post.id}"
+
+ click_on 'Show Content'
+
+ expect(find_field_value_element('body')).to have_text body
+ end
+ end
+
+ context 'edit' do
+ it 'has the posts body label' do
+ visit "/avo/resources/posts/#{post.id}/edit"
+
+ body_element = find_field_element('body')
+
+ expect(body_element).to have_text 'Body'
+ end
+
+ it 'has filled simple text in trix editor' do
+ visit "/avo/resources/posts/#{post.id}/edit"
+
+ expect(find_trix_editor('trixEditor').value).to eq('
' + body + '
')
+ end
+
+ it 'change the posts body trix to another simple text value' do
+ visit "/avo/resources/posts/#{post.id}/edit"
+
+ fill_in_trix_editor 'trixEditor', with: 'New example!'
+
+ click_on 'Save'
+ wait_for_loaded
+
+ click_on 'Show Content'
+
+ expect(find_field_value_element('body')).to have_text 'New example!'
+ end
+ end
+ end
+
+ def fill_in_trix_editor(id, with:)
+ find(:xpath, "//trix-editor[@input='#{id}']").click.set(with)
+ end
+
+ def find_trix_editor(id)
+ find(:xpath, "//*[@id='#{id}']", visible: false)
+ end
+end
diff --git a/tailwind.config.js b/tailwind.config.js
index cac295ee12..98f246d4f5 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -77,6 +77,7 @@ module.exports = {
cursor: ['responsive', 'disabled'],
},
plugins: [
+ require('@tailwindcss/typography'),
// buttons
plugin(({ addComponents, theme }) => {
const styles = {
diff --git a/yarn.lock b/yarn.lock
index 664b07008d..ed58d1a39a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -863,6 +863,11 @@
webpack-cli "^3.3.10"
webpack-sources "^1.4.3"
+"@tailwindcss/typography@^0.2.0":
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.2.0.tgz#b597c83502e3c3c6641a8aaabda223cd494ab349"
+ integrity sha512-aPgMH+CjQiScLZculoDNOQUrrK2ktkbl3D6uCLYp1jgYRlNDrMONu9nMu8LfwAeetYNpVNeIGx7WzHSu0kvECg==
+
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@@ -7968,6 +7973,11 @@ trim-newlines@^1.0.0:
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
+trix@^1.2.3:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/trix/-/trix-1.2.4.tgz#8b8f8ea58c2605ad3d04a3b82d60a994576e33a5"
+ integrity sha512-S6YUUaaHngNbW1jzBV3MQ35pisTV/x5yKlNZrAf8exkOnHsTj+2OJHx2jBLqRQEtpWHpg85pHjo2A7dKK2RQbQ==
+
"true-case-path@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"
@@ -8373,6 +8383,13 @@ vue-toasted@^1.1.28:
resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.28.tgz#dbabb83acc89f7a9e8765815e491d79f0dc65c26"
integrity sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw==
+vue-trix@^1.1.11:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/vue-trix/-/vue-trix-1.1.11.tgz#02a7e00739c7c5243a90fe5927480a192484ba65"
+ integrity sha512-+5tY9ocpCpQ61qeJpaYEvQDvMb3kE5K33k51YiZna/lFlpLoas4lZAvZwBkvwf7TjfG9Kb5kh0FKNiqpSs7ARQ==
+ dependencies:
+ trix "^1.2.3"
+
vue-turbolinks@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/vue-turbolinks/-/vue-turbolinks-2.1.0.tgz#fc7372d06d624aa3fb68231dc0b509719edc778f"