caboose-cms 0.8.79 → 0.8.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cbd447044fa644c9f566598042ea9b67b7f23faa
4
- data.tar.gz: 4cf0ab42e9c3abb072dbb4a6a738d8a1026b2292
3
+ metadata.gz: bd650e7422bd0162938f8d1478473b941330e299
4
+ data.tar.gz: b90ef95467affa299943c9d950b232f288944630
5
5
  SHA512:
6
- metadata.gz: 28d6a8ae3983ca9b3856ce3951878223c5789794c06d1aa949bf577513fa167e505852d5cd47e29e632127b3ef7e9099cb7204e5963e337d3b7418f800a7e354
7
- data.tar.gz: 7e958ad18045db7b8b6dadf7fca9e12b13ee00a59ad2784ad0335a182f9ba128f2b582f988fda26a885646e9daa9eb204dc7f90a30dca6a7e77ac0cbe8c33490
6
+ metadata.gz: 0410f10cb892116bdbe0b19746b11841dfcf1f21e2c16237f29815755353ff6588d954bc6f9cb047d3207d85043494ceb66c54fc3d91515465b226d16af19729
7
+ data.tar.gz: e07c5de3582548172e8b0fc9ac65fd3b35a19bfc8ac313849ca423c29509a92c11fa0e1b5d47becce4752e943f26173318088b9ae9fe3fbcc3512bacdbce88af
@@ -0,0 +1,157 @@
1
+
2
+ var AdminBlockTypeParserController = function(params) { this.init(params); };
3
+
4
+ AdminBlockTypeParserController.prototype = {
5
+
6
+ container: 'block_type_parser',
7
+ authenticity_token: false,
8
+ //original_html: "<div data-num='30' data-thumb='assets/minimalist-basic/thumbnails/p02.png' data-cat='0,13'>\n <div class='row clearfix'>\n <div class='column half'>\n <div class='display'>\n <h1>Beautiful content. Responsive.</h1>\n <p>Lorem Ipsum is simply dummy text.</p>\n <div style='margin:1em 0 2.5em;'>\n <a href='#' class='btn btn-primary edit'>Read More</a>\n </div>\n </div>\n </div>\n <div class='column half'>\n <img src='assets/minimalist-basic/p02-1.jpg'>\n </div>\n </div>\n</div>\n",
9
+ //original_html: "<div data-num='1' data-thumb='assets/minimalist-basic/thumbnails/a01.png' data-cat='1'>\n <div class='row clearfix'>\n <div class='column full'>\n <h1 class='size-96 is-title1-96 is-title-lite'>Lorem Ipsum</h1>\n </div>\n </div>\n</div>\n",
10
+ original_html: "" +
11
+ "<div data-num='30' data-thumb='assets/minimalist-basic/thumbnails/p02.png' data-cat='0,13'>\n" +
12
+ " <div class='row clearfix'>\n" +
13
+ " <div class='column half'>\n" +
14
+ " <div class='display'>\n" +
15
+ " <h1>Beautiful content. Responsive.</h1>\n" +
16
+ " <p>Lorem Ipsum is simply dummy text.</p>\n" +
17
+ " <div style='margin:1em 0 2.5em;'>\n" +
18
+ " <a href='#' class='btn btn-primary edit'>Read More</a>\n" +
19
+ " </div>\n" +
20
+ " </div>\n" +
21
+ " </div>\n" +
22
+ " <div class='column half'>\n" +
23
+ " <img src='assets/minimalist-basic/p02-1.jpg'>\n" +
24
+ " </div>\n" +
25
+ " </div>\n" +
26
+ "</div>\n",
27
+ parsable_tags: { heading: 'Headings' , richtext: 'Rich Text' , img: 'Images' , link: 'Links' },
28
+ tags_to_parse: { heading: true , richtext: true , img: true , link: true },
29
+ render_function: false,
30
+ children: false,
31
+
32
+ init: function(params) {
33
+ var that = this;
34
+ for (var i in params)
35
+ this[i] = params[i];
36
+
37
+ that.print_step1();
38
+ },
39
+
40
+ print_step1: function()
41
+ {
42
+ var that = this;
43
+
44
+ var tr = $('<tr/>');
45
+ var all_checked = true;
46
+ $.each(that.parsable_tags, function(tag, name) { if (!that.tags_to_parse[tag]) { all_checked = false; return false; }});
47
+ tr.append($('<td/>').append($('<input/>').attr('type', 'checkbox').attr('id', 'all').prop('checked', all_checked).click(function() {
48
+ var checked = $(this).is(':checked');
49
+ $.each(that.parsable_tags, function(tag, name) { $('#'+tag).prop('checked', checked); });
50
+ })))
51
+ .append($('<td/>').append($('<label/>').attr('for', 'all').append('All')));
52
+ $.each(that.parsable_tags, function(tag, name) {
53
+ tr.append($('<td/>').append($('<input/>').attr('type', 'checkbox').attr('id', tag).val('1').prop('checked', that.tags_to_parse[tag])))
54
+ .append($('<td/>').append($('<label/>').attr('for', tag).append(name)));
55
+ });
56
+ var tags_table = $('<table/>').append($('<tbody/>').append(tr));
57
+
58
+ var div = $('<div/>')
59
+ .append(tags_table)
60
+ .append($('<p/>').append($('<textarea/>').attr('id', 'html').css('width', '90%').css('height', '400px').attr('placeholder', 'HTML to Parse').html(that.original_html)))
61
+ .append($('<div/>').attr('id', 'message'))
62
+ .append($('<p/>').append($('<input/>').attr('type', 'button').val("Parse!").click(function() {
63
+ that.original_html = $('#html').val();
64
+ $.each(that.parsable_tags, function(tag, name) { that.tags_to_parse[tag] = $('#'+tag).is(':checked'); });
65
+ that.parse_tags();
66
+ })));
67
+ $('#'+that.container).empty().append(div);
68
+ },
69
+
70
+ parse_tags: function(show_json)
71
+ {
72
+ var that = this;
73
+
74
+ var tags = [];
75
+ $.each(that.tags_to_parse, function(tag, checked) { if (checked) tags.push(tag); });
76
+ $.ajax({
77
+ url: '/admin/block-types/parse-tags',
78
+ type: 'post',
79
+ data: {
80
+ html: that.original_html,
81
+ tags: tags,
82
+ children: that.children
83
+ },
84
+ success: function(resp) {
85
+ that.last_response = resp;
86
+ that.original_html = resp.original_html;
87
+ that.render_function = resp.render_function;
88
+ that.children = resp.children;
89
+ },
90
+ async: false
91
+ });
92
+
93
+ if (show_json)
94
+ {
95
+ $('#'+that.container).empty()
96
+ .append($('<textarea/>').attr('id', 'json').css('width', '90%').css('height', '400px').html(JSON.stringify(that.last_response)))
97
+ .append($('<p/>')
98
+ .append($('<input/>').attr('type', 'button').val("< Back to HTML").click(function() { that.print_step1(); })).append(' ')
99
+ .append($('<input/>').attr('type', 'button').val("Show Parsed").click(function() { that.parse_tags(); }))
100
+ );
101
+ return;
102
+ }
103
+
104
+ var tbody = $('<tbody/>')
105
+ .append($('<tr/>')
106
+ .append($('<th/>').append('Field Type'))
107
+ .append($('<th/>').append('Name'))
108
+ .append($('<th/>').append('Description'))
109
+ .append($('<th/>').append('Default Value'))
110
+ .append($('<th/>').attr('colspan', '2').append('Child Values'))
111
+ );
112
+
113
+ var field_types = {
114
+ heading: 'Heading',
115
+ image2: 'Image',
116
+ button: 'Link',
117
+ richtext: 'Rich Text'
118
+ };
119
+ $.each(that.children, function(i, v) {
120
+
121
+ var tr = $('<tr/>')
122
+ .append(field_types[v.field_type])
123
+ .append($('<td/>').attr('valign', 'top').append($('<input/>').data('i', i).val(v.name ).on('keyup', function(e) { var x = $(this).val().toLowerCase().replace(' ', '_'); $(this).val(x); that.children[parseInt($(this).data('i'))].name = x; })))
124
+ .append($('<td/>').attr('valign', 'top').append($('<input/>').data('i', i).val(v.description ).on('keyup', function(e) { that.children[parseInt($(this).data('i'))].description = $(this).val(); })))
125
+ .append($('<td/>').attr('valign', 'top').append($('<input/>').data('i', i).val(v.default ).on('keyup', function(e) { that.children[parseInt($(this).data('i'))].default = $(this).val(); })));
126
+
127
+ if (v.child_values)
128
+ {
129
+ j = 0;
130
+ $.each(v.child_values, function(k,v) {
131
+ if (j > 0)
132
+ tr.append($('<td/>').attr('colspan', '4').html("&nbsp;"));
133
+ tr.append($('<td/>').attr('align', 'right').append(k))
134
+ .append($('<td/>').append($('<input/>').data('i', i).data('k', k).val(v).on('keyup', function(e) { that.children[parseInt($(this).data('i'))].child_values[$(this).data('k')] = $(this).val(); })));
135
+ tbody.append(tr);
136
+ tr = $('<tr/>');
137
+ j++;
138
+ });
139
+ }
140
+ else
141
+ tbody.append(tr);
142
+ });
143
+ var vars_table = $('<table/>').append(tbody);
144
+
145
+ $('#'+that.container).empty()
146
+ .append(vars_table)
147
+ .append($('<textarea/>').attr('id', 'parsed').css('width', '90%').css('height', '400px').html(that.render_function))
148
+ .append($('<p/>')
149
+ .append($('<input/>').attr('type', 'button').val("< Back to HTML").click(function() { that.print_step1(); })).append(' ')
150
+ .append($('<input/>').attr('type', 'button').val("Re-parse with Updated Variables").click(function() { that.parse_tags(); })).append(' ')
151
+ .append($('<input/>').attr('type', 'button').val("Show JSON").click(function() { that.parse_tags(true); }))
152
+ );
153
+ },
154
+ };
155
+
156
+
157
+
@@ -47,6 +47,20 @@ module Caboose
47
47
  block_type = BlockType.find(params[:id])
48
48
  render :json => block_type.as_json(:include => :sites)
49
49
  end
50
+
51
+ # @route GET /admin/block-types/parse
52
+ def admin_parse_form
53
+ return if !user_is_allowed('pages', 'view')
54
+ render :layout => 'caboose/admin'
55
+ end
56
+
57
+ # @route POST /admin/block-types/parse-tags
58
+ def admin_parse_tags
59
+ return if !user_is_allowed('pages', 'view')
60
+ children = params[:children] && params[:children] != 'false' ? params[:children] : nil
61
+ resp = BlockTypeParser.parse_html(params[:html], params[:tags], children)
62
+ render :json => resp
63
+ end
50
64
 
51
65
  # @route GET /admin/block-types/new
52
66
  # @route GET /admin/block-types/:id/new
@@ -0,0 +1,211 @@
1
+ require 'nokogiri'
2
+ require 'nokogiri-styles'
3
+
4
+ module Caboose
5
+ class BlockTypeParser
6
+
7
+ def BlockTypeParser.parse_html(str, tags, children = nil)
8
+
9
+ doc = Nokogiri::HTML.fragment(str)
10
+ doc.children.first.set_attribute('id', "block_<%= block.id %>")
11
+
12
+ count = 0
13
+ rf_header = []
14
+ rf_body = "#{doc.to_html}"
15
+ new_children = []
16
+ tags.each do |tag|
17
+ case tag
18
+ when 'heading'
19
+
20
+ headings = doc.search('h1') + doc.search('h2') + doc.search('h3') + doc.search('h4') + doc.search('h5') + doc.search('h6')
21
+ headings.each_with_index do |h, i|
22
+
23
+ info = children ? (children[count] ? children[count] : (children[count.to_s] ? children[count.to_s] : nil)) : nil
24
+ description = info && info['description'] ? info['description'] : "#{(i+1).ordinalize} Heading"
25
+ name = info && info['name'] ? info['name'] : description.downcase.gsub(' ', '_')
26
+
27
+ cv = StdClass.new
28
+ cv.heading_text = h.text
29
+ cv.size = h.name.gsub('h','').to_i
30
+ cv.align = h.attributes['align'].to_s if h.attributes['align']
31
+ cv.extra_classes = h.attributes['class'].to_s if h.attributes['class']
32
+ if h['style']
33
+ cv.align = h.styles['text-align'] if h.styles['text-align']
34
+ cv.color = h.styles['color'] if h.styles['color']
35
+ cv.margin_bottom = h.styles['margin-bottom'] if h.styles['margin-bottom']
36
+ cv.margin_top = h.styles['margin-top'] if h.styles['margin-top']
37
+ cv.underline = true if h.styles['text-decoration']
38
+ end
39
+
40
+ v = StdClass.new({
41
+ :name => name,
42
+ :description => description,
43
+ :field_type => 'heading',
44
+ :child_values => cv
45
+ })
46
+
47
+ rf_body.gsub!(h.to_s, "<%= block.render('#{name}') %>")
48
+ new_children << v
49
+
50
+ count = count + 1
51
+ end
52
+
53
+ when 'link'
54
+
55
+ links = doc.search('a')
56
+ links.each_with_index do |link, i|
57
+
58
+ info = children ? (children[count] ? children[count] : (children[count.to_s] ? children[count.to_s] : nil)) : nil
59
+ description = info && info['description'] ? info['description'] : "#{(i+1).ordinalize} Link"
60
+ name = info && info['name'] ? info['name'] : description.downcase.gsub(' ', '_')
61
+
62
+ cv = StdClass.new
63
+ cv.text = link.text
64
+ cv.align = link.attributes['align' ].to_s if link.attributes['align' ]
65
+ cv.target = link.attributes['target' ].to_s if link.attributes['target' ]
66
+ cv.url = link.attributes['href' ].to_s if link.attributes['href' ]
67
+ cv.extra_classes = link.attributes['class' ].to_s if link.attributes['class' ]
68
+ if link['style']
69
+ cv.align = link.styles['text-align' ] if link.styles['text-align' ]
70
+ cv.color = link.styles['color' ] if link.styles['color' ]
71
+ cv.margin = link.styles['margin' ] if link.styles['margin-bottom' ]
72
+ cv.margin_top = link.styles['margin-top' ] if link.styles['margin-top' ]
73
+ cv.margin_bottom = link.styles['margin-bottom'] if link.styles['margin-bottom' ]
74
+ cv.margin_left = link.styles['margin-left' ] if link.styles['margin-left' ]
75
+ cv.margin_right = link.styles['margin-right' ] if link.styles['margin-right' ]
76
+ end
77
+
78
+ v = StdClass.new({
79
+ :name => name,
80
+ :description => description,
81
+ :field_type => 'button',
82
+ :child_values => cv
83
+ })
84
+
85
+ rf_body.gsub!(link.to_s, "<%= block.render('#{name}') %>")
86
+ new_children << v
87
+
88
+ count = count + 1
89
+ end
90
+
91
+ when 'richtext'
92
+
93
+ paragraphs = doc.search('p')
94
+ paragraphs.each_with_index do |p, i|
95
+
96
+ info = children ? (children[count] ? children[count] : (children[count.to_s] ? children[count.to_s] : nil)) : nil
97
+ description = info && info['description'] ? info['description'] : "#{(i+1).ordinalize} Richtext"
98
+ name = info && info['name'] ? info['name'] : description.downcase.gsub(' ', '_')
99
+
100
+ v = StdClass.new({
101
+ :name => name,
102
+ :description => description,
103
+ :field_type => 'richtext',
104
+ :default => p.text
105
+ })
106
+
107
+ rf_body.gsub!(p.to_s, "<%= block.render('#{name}') %>")
108
+ new_children << v
109
+
110
+ count = count + 1
111
+ end
112
+
113
+ when 'img'
114
+
115
+ images = doc.search('img')
116
+ images.each_with_index do |img, i|
117
+
118
+ info = children ? (children[count] ? children[count] : (children[count.to_s] ? children[count.to_s] : nil)) : nil
119
+ description = info && info['description'] ? info['description'] : "#{(i+1).ordinalize} Image"
120
+ name = info && info['name'] ? info['name'] : description.downcase.gsub(' ', '_')
121
+
122
+ cv = StdClass.new
123
+ cv.image_src = img.attributes['src' ].to_s
124
+ cv.alt_text = img.attributes['alt' ].to_s if img.attributes['alt' ]
125
+ cv.width = img.attributes['width' ].to_s if img.attributes['width' ]
126
+ cv.height = img.attributes['height' ].to_s if img.attributes['height' ]
127
+ cv.width = img.styles['width' ] if img.styles['width' ]
128
+ cv.height = img.styles['height' ] if img.styles['height' ]
129
+ cv.align = img.styles['float' ] if img.styles['float' ]
130
+ cv.margin_bottom = img.styles['margin-bottom' ] if img.styles['margin-bottom' ]
131
+ cv.margin_left = img.styles['margin-left' ] if img.styles['margin-left' ]
132
+ cv.margin_right = img.styles['margin-right' ] if img.styles['margin-right' ]
133
+ cv.margin_top = img.styles['margin-top' ] if img.styles['margin-top' ]
134
+
135
+ Caboose.log(cv)
136
+
137
+ v = StdClass.new({
138
+ :name => name,
139
+ :description => description,
140
+ :field_type => 'image2',
141
+ :child_values => cv
142
+ })
143
+
144
+ rf_body.gsub!(img.to_s, "<%= block.render('#{name}') %>")
145
+ new_children << v
146
+
147
+ count = count + 1
148
+ end
149
+
150
+ end
151
+ end
152
+
153
+ render_function = ""
154
+ render_function << "<%\n#{rf_header.join("\n")}\n%>\n" if rf_header.count > 0
155
+ render_function << rf_body
156
+
157
+ return {
158
+ :original_html => str,
159
+ :render_function => render_function,
160
+ :children => new_children
161
+ }
162
+ end
163
+
164
+ def BlockTypeParser.parse(str)
165
+
166
+ vars = {}
167
+ pattern = /<%= \|(.*?)\| %>/
168
+ new_lines = []
169
+ lines = str.split("\n")
170
+ lines.each do |line|
171
+ matches = line.to_enum(:scan, pattern).map{$&}
172
+ if matches
173
+ matches.each do |match|
174
+ next if match.nil? || match.length == 0
175
+ arr = match[5..-5].split('|')
176
+
177
+ name = arr.count > 0 ? arr[0] : nil
178
+ description = arr.count > 1 ? arr[1] : nil
179
+ field_type = arr.count > 2 ? arr[2] : nil
180
+ default = arr.count > 3 ? arr[3] : nil
181
+ default = default && ((default.starts_with?('"') && default.ends_with?('"')) || (default.starts_with?("'") && default.ends_with?("'"))) ? default[1..-2] : nil
182
+
183
+ if vars[name.to_sym].nil?
184
+ vars[name.to_sym] = StdClass.new({
185
+ :name => name ,
186
+ :description => description ,
187
+ :field_type => field_type ,
188
+ :default => default
189
+ })
190
+ end
191
+ line.gsub!(match, "<%= #{name} %>")
192
+ end
193
+ end
194
+ new_lines << line
195
+ end
196
+
197
+ str2 = "<%\n"
198
+ vars.each do |k,var|
199
+ str2 << "#{var.name} = block.child_value('#{var.name}')\n"
200
+ end
201
+ str2 << "\n"
202
+ vars.each do |k,var|
203
+ str2 << "#{var.name} = \"#{var.default.gsub('"', '\"')}\" if #{var.name}.nil? || #{var.name}.length == 0\n" if var.default
204
+ end
205
+ str2 << "%>\n"
206
+ str2 << new_lines.join("\n")
207
+ return str2
208
+ end
209
+
210
+ end
211
+ end
@@ -4,7 +4,12 @@
4
4
  </div>
5
5
 
6
6
  <h1>Block Types</h1>
7
+
8
+ <!--
7
9
  <p><a href='/admin/block-types/store'>Block Type Store</a></p>
10
+ -->
11
+ <p><a href='/admin/block-types/parse'>Block Type Parser</a></p>
12
+
8
13
  <div id='block_types'></div>
9
14
  <% (0..100).each do |i| %><p>&nbsp;</p><% end %>
10
15
 
@@ -0,0 +1,35 @@
1
+ <div id='crumbtrail'>
2
+ <a href='/admin'>Admin</a> >
3
+ <a href='/admin/block-types'>Block Types</a> >
4
+ Parser
5
+ </div>
6
+
7
+ <h1>Block Type Parser</h1>
8
+ <p>
9
+ <a href='/admin/block-types'>Block Types List</a> |
10
+ <a href='/admin/block-types/parse'>Parser</a>
11
+ </p>
12
+
13
+ <div id='block_type_parser'></div>
14
+
15
+ <% content_for :caboose_js do %>
16
+ <%= javascript_include_tag 'caboose/model/all' %>
17
+ <%= javascript_include_tag 'caboose/admin_block_type_parser_controller' %>
18
+ <script type='text/javascript'>
19
+
20
+ var controller = false;
21
+ $(document).ready(function() {
22
+ controller = new AdminBlockTypeParserController({
23
+ form_authenticity_token: '<%= form_authenticity_token %>'
24
+ });
25
+ });
26
+
27
+ </script>
28
+ <% end %>
29
+ <% content_for :caboose_css do %>
30
+ <style type='text/css'>
31
+
32
+ textarea { font-family: Courier; font-size: 10pt; white-space: pre; overflow-wrap: normal; overflow-x: scroll; }
33
+
34
+ </style>
35
+ <% end %>
@@ -1,3 +1,3 @@
1
1
  module Caboose
2
- VERSION = '0.8.79'
2
+ VERSION = '0.8.80'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caboose-cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.79
4
+ version: 0.8.80
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Barry
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-14 00:00:00.000000000 Z
11
+ date: 2016-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -220,6 +220,20 @@ dependencies:
220
220
  - - ">="
221
221
  - !ruby/object:Gem::Version
222
222
  version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: nokogiri-styles
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
223
237
  - !ruby/object:Gem::Dependency
224
238
  name: delayed_job_active_record
225
239
  requirement: !ruby/object:Gem::Requirement
@@ -513,6 +527,7 @@ files:
513
527
  - app/assets/images/caboose/search.png
514
528
  - app/assets/javascripts/caboose/admin.js
515
529
  - app/assets/javascripts/caboose/admin_block_edit.js
530
+ - app/assets/javascripts/caboose/admin_block_type_parser_controller.js
516
531
  - app/assets/javascripts/caboose/admin_edit_invoice.js
517
532
  - app/assets/javascripts/caboose/admin_edit_modifications.js
518
533
  - app/assets/javascripts/caboose/admin_main.js
@@ -841,6 +856,7 @@ files:
841
856
  - app/models/caboose/block_type.rb
842
857
  - app/models/caboose/block_type_category.rb
843
858
  - app/models/caboose/block_type_icons.rb
859
+ - app/models/caboose/block_type_parser.rb
844
860
  - app/models/caboose/block_type_site_membership.rb
845
861
  - app/models/caboose/block_type_source.rb
846
862
  - app/models/caboose/block_type_summary.rb
@@ -956,6 +972,7 @@ files:
956
972
  - app/views/caboose/block_types/admin_edit_icon.html.erb
957
973
  - app/views/caboose/block_types/admin_index.html.erb
958
974
  - app/views/caboose/block_types/admin_new.html.erb
975
+ - app/views/caboose/block_types/admin_parse_form.html.erb
959
976
  - app/views/caboose/blocks/_block.html.erb
960
977
  - app/views/caboose/blocks/_checkbox.html.erb
961
978
  - app/views/caboose/blocks/_controller_view_content.html.erb