Skip to content

Commit 622b95d

Browse files
feature: Trix field (#107)
Co-authored-by: Adrian Marin <[email protected]>
1 parent 8761ee9 commit 622b95d

File tree

12 files changed

+267
-33
lines changed

12 files changed

+267
-33
lines changed

Diff for: app/frontend/js/components/Edit/TrixField.vue

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<template>
2+
<edit-field-wrapper :field="field" :errors="errors" :index="index" :value-slot-full-width="true">
3+
<VueTrix v-model="editorContent"
4+
:placeholder="field.placeholder"
5+
class="w-full prose prose-sm"
6+
inputId="trixEditor"
7+
/>
8+
</edit-field-wrapper>
9+
</template>
10+
11+
<script>
12+
import FormField from '@/js/mixins/form-field'
13+
import VueTrix from 'vue-trix'
14+
15+
export default {
16+
mixins: [FormField],
17+
components: {
18+
VueTrix,
19+
},
20+
data() {
21+
return {
22+
editorContent: this.field.value,
23+
}
24+
},
25+
methods: {
26+
setInitialValue() {
27+
this.editorContent = this.field.value
28+
},
29+
getValue() {
30+
return this.editorContent
31+
},
32+
focus() {
33+
if (this.$refs['field-input']) this.$refs['field-input'].$emit('focus')
34+
},
35+
},
36+
}
37+
</script>
38+
39+
<style>
40+
.trix-content pre {
41+
background-color: #2D3648 !important;
42+
}
43+
</style>

Diff for: app/frontend/js/components/Show/TrixField.vue

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<template>
2+
<show-field-wrapper :field="field" :index="index" :value-slot-full-width="true">
3+
<div v-if="field.value">
4+
<div v-if="showTrix" v-html="field.value" class="prose prose-sm"/>
5+
<a href="javascript:void(0);"
6+
v-if="!field.always_show"
7+
:class="buttonStyle"
8+
@click="toggleTrix"
9+
v-text="linkLabel"
10+
/>
11+
</div>
12+
<empty-dash v-else />
13+
</show-field-wrapper>
14+
</template>
15+
16+
<script>
17+
export default {
18+
props: ['field', 'index'],
19+
data() {
20+
return {
21+
showTrix: this.field.always_show,
22+
}
23+
},
24+
computed: {
25+
buttonStyle() {
26+
if (this.showTrix) return 'font-bold inline-block mt-6'
27+
28+
return 'font-bold'
29+
},
30+
linkLabel() {
31+
return this.showTrix ? 'Hide Content' : 'Show Content'
32+
},
33+
},
34+
methods: {
35+
toggleTrix() {
36+
this.showTrix = !this.showTrix
37+
},
38+
},
39+
}
40+
</script>
41+
42+
<style>
43+
.prose {
44+
max-width: none !important;
45+
}
46+
</style>

Diff for: app/frontend/js/components/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Vue.component('show-country-field', require('@/js/components/Sh
4848
Vue.component('show-badge-field', require('@/js/components/Show/BadgeField.vue').default)
4949
Vue.component('show-heading-field', require('@/js/components/Show/HeadingField.vue').default)
5050
Vue.component('show-code-field', require('@/js/components/Show/CodeField.vue').default)
51+
Vue.component('show-trix-field', require('@/js/components/Show/TrixField.vue').default)
5152

5253
Vue.component('edit-field-wrapper', require('@/js/components/Edit/FieldWrapper.vue').default)
5354
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
7071
Vue.component('edit-heading-field', require('@/js/components/Show/HeadingField.vue').default)
7172
Vue.component('edit-code-field', require('@/js/components/Edit/CodeField.vue').default)
7273
Vue.component('edit-hidden-field', require('@/js/components/Edit/HiddenField.vue').default)
74+
Vue.component('edit-trix-field', require('@/js/components/Edit/TrixField.vue').default)
7375

7476
// Form Fields
7577
Vue.component('input-component', require('@/js/components/InputComponent.vue').default)

Diff for: lib/avo/app/fields/trix_field.rb

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require_relative 'field'
2+
3+
module Avo
4+
module Fields
5+
class TrixField < Field
6+
def initialize(name, **args, &block)
7+
@defaults = {
8+
component: 'trix-field',
9+
}
10+
11+
super(name, **args, &block)
12+
13+
hide_on :index
14+
15+
@always_show = args[:always_show].present? ? args[:always_show] : false
16+
end
17+
18+
def hydrate_field(fields, model, resource, view)
19+
{
20+
always_show: @always_show
21+
}
22+
end
23+
end
24+
end
25+
end

Diff for: lib/avo/app/resource_fields.rb

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def get_fields
1616
def add_field(resource, field)
1717
@@fields[resource].push field
1818
end
19+
20+
def trix(name, **args, &block)
21+
@@fields[self].push Avo::Fields::TrixField::new(name, **args, &block)
22+
end
1923
end
2024
end
2125
end

Diff for: package.json

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dependencies": {
2020
"@rails/activestorage": "^6.0.2-1",
2121
"@rails/webpacker": "^4.2.2",
22+
"@tailwindcss/typography": "^0.2.0",
2223
"autoprefixer": "^9.7.4",
2324
"axios": "^0.19.2",
2425
"css-loader": "^3.5.3",
@@ -58,6 +59,7 @@
5859
"vue-svg-loader": "^0.16.0",
5960
"vue-template-compiler": "^2.6.11",
6061
"vue-toasted": "^1.1.28",
62+
"vue-trix": "^1.1.11",
6163
"vue-turbolinks": "^2.1.0",
6264
"vuejs-paginate": "^2.1.0",
6365
"vuex": "^3.5.1",

Diff for: spec/dummy/app/avo/resources/post.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ def initialize
1111
fields do
1212
id
1313
text :name, required: true
14-
textarea :body, nullable: true, null_values: ['0', '', 'null', 'nil'], format_using: -> (value) { value.to_s.truncate 100 }
14+
trix :body, placeholder: 'Enter text', always_show: false
1515
file :cover_photo, is_image: true
1616
boolean :is_featured
1717
boolean :is_published do |model|
1818
model.published_at.present?
1919
end
2020

21+
2122
belongs_to :user, searchable: false, placeholder: '—'
2223
end
2324

Diff for: spec/dummy/app/avo/resources/team.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def initialize
99
fields do
1010
id
1111
text :name
12-
textarea :description, rows: 5, readonly: false, hide_on: :index, format_using: -> (value) { value.to_s.truncate 30 }, required: true, default: 'This team is wonderful!'
12+
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']
1313

1414
number :members_count do |model|
1515
model.members.count

Diff for: spec/system/avo/nullable_field_spec.rb

+31-31
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,76 @@
22

33
RSpec.describe 'NullableField', type: :system do
44
describe 'without input (specifying null_values: ["", "0", "null", "nil"])' do
5-
let!(:post) { create :post, body: nil }
5+
let!(:team) { create :team, description: nil }
66

77
context 'show' do
8-
it 'displays the posts empty body (dash)' do
9-
visit "/avo/resources/post/#{post.id}"
8+
it 'displays the teams empty description (dash)' do
9+
visit "/avo/resources/teams/#{team.id}"
1010

11-
expect(find_field_value_element('body')).to have_text empty_dash
11+
expect(find_field_value_element('description')).to have_text empty_dash
1212
end
1313
end
1414

1515
context 'edit' do
16-
it 'has the posts body empty' do
17-
visit "/avo/resources/posts/#{post.id}/edit"
16+
it 'has the teams description empty' do
17+
visit "/avo/resources/teams/#{team.id}/edit"
1818

19-
expect(find_field('body').value).to eq ''
19+
expect(find_field('description').value).to eq ''
2020
end
2121
end
2222
end
2323

2424
describe 'with regular input (specifying null_values: ["", "0", "null", "nil"])' do
25-
let!(:post) { create :post, body: 'descr' }
25+
let!(:team) { create :team, description: 'descr' }
2626

2727
context 'edit' do
28-
it 'has the posts body prefilled' do
29-
visit "/avo/resources/posts/#{post.id}/edit"
28+
it 'has the teams description prefilled' do
29+
visit "/avo/resources/teams/#{team.id}/edit"
3030

31-
expect(find_field('body').value).to eq 'descr'
31+
expect(find_field('description').value).to eq 'descr'
3232
end
33-
it 'changes the posts body to null ("" - empty string)' do
34-
visit "/avo/resources/posts/#{post.id}/edit"
33+
it 'changes the teams description to null ("" - empty string)' do
34+
visit "/avo/resources/teams/#{team.id}/edit"
3535

36-
fill_in 'body', with: ''
36+
fill_in 'description', with: ''
3737
click_on 'Save'
3838
wait_for_loaded
3939

40-
expect(current_path).to eql "/avo/resources/posts/#{post.id}"
41-
expect(find_field_value_element('body')).to have_text empty_dash
40+
expect(current_path).to eql "/avo/resources/teams/#{team.id}"
41+
expect(find_field_value_element('description')).to have_text empty_dash
4242
end
4343

44-
it 'changes the posts body to null ("0")' do
45-
visit "/avo/resources/posts/#{post.id}/edit"
44+
it 'changes the teams description to null ("0")' do
45+
visit "/avo/resources/teams/#{team.id}/edit"
4646

47-
fill_in 'body', with: '0'
47+
fill_in 'description', with: '0'
4848
click_on 'Save'
4949
wait_for_loaded
5050

51-
expect(current_path).to eql "/avo/resources/posts/#{post.id}"
52-
expect(find_field_value_element('body')).to have_text empty_dash
51+
expect(current_path).to eql "/avo/resources/teams/#{team.id}"
52+
expect(find_field_value_element('description')).to have_text empty_dash
5353
end
5454

55-
it 'changes the posts body to null ("nil")' do
56-
visit "/avo/resources/posts/#{post.id}/edit"
55+
it 'changes the teams description to null ("nil")' do
56+
visit "/avo/resources/teams/#{team.id}/edit"
5757

58-
fill_in 'body', with: 'nil'
58+
fill_in 'description', with: 'nil'
5959
click_on 'Save'
6060
wait_for_loaded
6161

62-
expect(current_path).to eql "/avo/resources/posts/#{post.id}"
63-
expect(find_field_value_element('body')).to have_text empty_dash
62+
expect(current_path).to eql "/avo/resources/teams/#{team.id}"
63+
expect(find_field_value_element('description')).to have_text empty_dash
6464
end
6565

66-
it 'changes the postss body to null ("null")' do
67-
visit "/avo/resources/posts/#{post.id}/edit"
66+
it 'changes the teams description to null ("null")' do
67+
visit "/avo/resources/teams/#{team.id}/edit"
6868

69-
fill_in 'body', with: 'null'
69+
fill_in 'description', with: 'null'
7070
click_on 'Save'
7171
wait_for_loaded
7272

73-
expect(current_path).to eql "/avo/resources/posts/#{post.id}"
74-
expect(find_field_value_element('body')).to have_text empty_dash
73+
expect(current_path).to eql "/avo/resources/teams/#{team.id}"
74+
expect(find_field_value_element('description')).to have_text empty_dash
7575
end
7676
end
7777
end

Diff for: spec/system/avo/trix_field_spec.rb

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
require 'rails_helper'
2+
3+
RSpec.describe 'TrixField', type: :system do
4+
describe 'without value' do
5+
let!(:post) { create :post , body: '' }
6+
7+
context 'show' do
8+
it 'displays the posts empty body (dash)' do
9+
visit "/avo/resources/posts/#{post.id}"
10+
11+
expect(find_field_element('body')).to have_text empty_dash
12+
end
13+
end
14+
15+
context 'edit' do
16+
it 'has the posts body label and empty trix editor and placeholder' do
17+
visit "/avo/resources/posts/#{post.id}/edit"
18+
19+
body_element = find_field_element('body')
20+
21+
expect(body_element).to have_text 'Body'
22+
23+
expect(find(:xpath, "//trix-editor[@input='trixEditor']")[:placeholder]).to have_text('Enter text')
24+
expect(find_trix_editor('trixEditor')).to have_text('')
25+
end
26+
27+
it 'change the posts body text' do
28+
visit "/avo/resources/posts/#{post.id}/edit"
29+
30+
fill_in_trix_editor 'trixEditor', with: 'Works for us!!!'
31+
32+
click_on 'Save'
33+
wait_for_loaded
34+
35+
click_on 'Show Content'
36+
37+
expect(find_field_value_element('body')).to have_text 'Works for us!!!'
38+
end
39+
end
40+
end
41+
42+
describe 'with regular value' do
43+
let!(:body) { 'Example trix text.' }
44+
let!(:post) { create :post , body: body }
45+
46+
context 'show' do
47+
it 'displays the posts body' do
48+
visit "/avo/resources/posts/#{post.id}"
49+
50+
click_on 'Show Content'
51+
52+
expect(find_field_value_element('body')).to have_text body
53+
end
54+
end
55+
56+
context 'edit' do
57+
it 'has the posts body label' do
58+
visit "/avo/resources/posts/#{post.id}/edit"
59+
60+
body_element = find_field_element('body')
61+
62+
expect(body_element).to have_text 'Body'
63+
end
64+
65+
it 'has filled simple text in trix editor' do
66+
visit "/avo/resources/posts/#{post.id}/edit"
67+
68+
expect(find_trix_editor('trixEditor').value).to eq('<div>' + body + '</div>')
69+
end
70+
71+
it 'change the posts body trix to another simple text value' do
72+
visit "/avo/resources/posts/#{post.id}/edit"
73+
74+
fill_in_trix_editor 'trixEditor', with: 'New example!'
75+
76+
click_on 'Save'
77+
wait_for_loaded
78+
79+
click_on 'Show Content'
80+
81+
expect(find_field_value_element('body')).to have_text 'New example!'
82+
end
83+
end
84+
end
85+
86+
def fill_in_trix_editor(id, with:)
87+
find(:xpath, "//trix-editor[@input='#{id}']").click.set(with)
88+
end
89+
90+
def find_trix_editor(id)
91+
find(:xpath, "//*[@id='#{id}']", visible: false)
92+
end
93+
end

Diff for: tailwind.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ module.exports = {
7777
cursor: ['responsive', 'disabled'],
7878
},
7979
plugins: [
80+
require('@tailwindcss/typography'),
8081
// buttons
8182
plugin(({ addComponents, theme }) => {
8283
const styles = {

0 commit comments

Comments
 (0)