alchemy-ajax-form 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -3
- data/app/assets/javascripts/ajax_forms.js.erb +133 -132
- data/app/assets/stylesheets/alchemy/ajax/form/backend/custom_resource_show.scss +21 -0
- data/app/assets/stylesheets/alchemy/ajax/form/style.scss +17 -0
- data/app/controllers/alchemy/admin/ajax_forms_controller.rb +8 -0
- data/app/helpers/alchemy/admin/ajax_forms_helper.rb +17 -0
- data/app/views/alchemy/admin/ajax_forms/_resource.html.erb +36 -0
- data/app/views/alchemy/admin/ajax_forms/show.html.erb +12 -0
- data/config/locales/it.yml +1 -0
- data/lib/alchemy/ajax/form/version.rb +1 -1
- data/lib/generators/custom_form/custom_form_generator.rb +14 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4802d959a964e57c819c0cf72c81e9c99d298c45
|
4
|
+
data.tar.gz: defa090c069a91d5a3482d19ff606a0d18c91c4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f9631c383b34fc1461fdbe39ee192635b3fa9919f5e4281e2eb19faa9d87382aa4d415d33be8470ce2e592b3e0f44a8687504b017961b4ae3cdf8eacfdf8a8b
|
7
|
+
data.tar.gz: e0e7063fb6ebca6adb7397d2601c7ed55ab9db9bbc1ea985dccbbfd10189dd14d3c220920580d0ddb95982a4f49797b4158d4ffd998692e80e92f13c1cbdd9e4
|
data/README.md
CHANGED
@@ -288,6 +288,18 @@ Add to app/assets/javascripts/application.js
|
|
288
288
|
//= require ajax_forms
|
289
289
|
```
|
290
290
|
|
291
|
+
Add to vendor/assets/stylesheets/alchemy/admin/all.css
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
*= require alchemy/ajax/form/backend/custom_resource_show
|
295
|
+
```
|
296
|
+
|
297
|
+
Add to app/assets/stylesheets/application.css
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
*= require alchemy/ajax/form/style
|
301
|
+
```
|
302
|
+
|
291
303
|
### Run Migration
|
292
304
|
Run migration with rake task
|
293
305
|
|
@@ -302,9 +314,12 @@ Restart Server
|
|
302
314
|
## Translations
|
303
315
|
Remember to check translations
|
304
316
|
|
305
|
-
|
306
|
-
|
307
|
-
|
317
|
+
## Disable MJML
|
318
|
+
If you don't want mjml, insert in an initializer
|
319
|
+
```ruby
|
320
|
+
Alchemy::Ajax::Form.enable_mjml= false
|
321
|
+
```
|
322
|
+
n.b. Disable mjml before launching the generator, otherwise email templates will be generated with mjml
|
308
323
|
|
309
324
|
## License
|
310
325
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -1,185 +1,186 @@
|
|
1
1
|
(function ($) {
|
2
2
|
|
3
|
-
|
3
|
+
var recaptha_disabled =
|
4
|
+
<%= Recaptcha.configuration.skip_verify_env.include?(Rails.env.to_s ) %>
|
4
5
|
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
var print_results = function (element, result) {
|
8
|
+
if (result instanceof Array) {
|
9
|
+
var resp = "";
|
9
10
|
|
10
|
-
|
11
|
+
$.each(result, function (index, value) {
|
11
12
|
|
12
|
-
|
13
|
+
resp += "<span>" + value + "</span>";
|
13
14
|
|
14
|
-
|
15
|
+
});
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
} else {
|
18
|
+
resp = result
|
19
|
+
}
|
19
20
|
|
20
|
-
|
21
|
-
|
21
|
+
resp = '<div class="default_message">' + resp + '</div>';
|
22
|
+
$(element).append(resp);
|
22
23
|
|
23
|
-
|
24
|
+
};
|
24
25
|
|
25
|
-
|
26
|
+
var add_errors = function (form, errors) {
|
26
27
|
|
27
|
-
|
28
|
+
$.each(errors, function (name, value) {
|
28
29
|
|
29
|
-
|
30
|
+
var input = $(form).find("[name*='" + name + "']");
|
30
31
|
|
31
32
|
|
32
|
-
|
33
|
+
$(input).addClass("invalid");
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
if ($(input).parent().children('.error-message').length == 0) {
|
36
|
+
$(input).parent().append("<span class='error-message'>" + value + "</span>");
|
37
|
+
} else {
|
38
|
+
$('.error-message').show();
|
39
|
+
}
|
39
40
|
|
40
|
-
|
41
|
-
|
41
|
+
});
|
42
|
+
};
|
42
43
|
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
var clear_deafult_message = function (form) {
|
46
|
+
var message_box = $(form).parent().find(".messages")[0];
|
47
|
+
var default_message = $(message_box).find(".default_message");
|
48
|
+
var custom_message = $(message_box).find(".custom_message");
|
49
|
+
if ($(default_message).length > 0) {
|
50
|
+
$(default_message).remove();
|
51
|
+
}
|
51
52
|
|
52
|
-
|
53
|
-
|
53
|
+
if ($(custom_message).length == 0) {
|
54
|
+
$(message_box).html("");
|
55
|
+
}
|
54
56
|
}
|
55
|
-
}
|
56
57
|
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
var add_loader = function (form) {
|
60
|
+
if ($(form).children('.ajax_forms_loader').length == 0) {
|
61
|
+
$(form).append('<div class="ajax_forms_loader"> <i class="fa fa-circle-o-notch fa-spin"></i> </div>');
|
62
|
+
} else {
|
63
|
+
$(form).children('.ajax_forms_loader').fadeIn();
|
64
|
+
}
|
63
65
|
}
|
64
|
-
}
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
}
|
69
|
-
|
70
|
-
var submit_function = function (form, method, action) {
|
71
|
-
$(form).trigger("submitted");
|
72
|
-
add_loader(form);
|
73
|
-
|
74
|
-
if ($(form).find("[name='_dontcare']").length < 1) {
|
75
|
-
$(form).append("<input type=\"hidden\" name=\"_dontcare\">")
|
67
|
+
var hide_loader = function (form) {
|
68
|
+
$(form).children('.ajax_forms_loader').fadeOut();
|
76
69
|
}
|
77
70
|
|
78
|
-
var
|
79
|
-
|
80
|
-
|
81
|
-
url: action,
|
82
|
-
dataType: "json",
|
83
|
-
data: formdata,
|
84
|
-
contentType: false,
|
85
|
-
processData: false,
|
86
|
-
success: function (response, status, xhr) {
|
87
|
-
|
88
|
-
clear_deafult_message(form);
|
89
|
-
var message_box = $(form).parent().find(".messages")[0];
|
90
|
-
if (!$(message_box).find(".custom_message").length > 0) {
|
91
|
-
print_results(message_box, response.messages);
|
92
|
-
}
|
93
|
-
|
94
|
-
$(form).trigger("successed");
|
95
|
-
$(message_box).addClass('ok');
|
96
|
-
$(form).trigger("reset");
|
71
|
+
var submit_function = function (form, method, action) {
|
72
|
+
$(form).trigger("submitted");
|
73
|
+
add_loader(form);
|
97
74
|
|
98
|
-
|
99
|
-
|
100
|
-
var response = JSON.parse(xhr.responseText);
|
101
|
-
var message_box = $(form).parent().find(".messages")[0];
|
102
|
-
clear_deafult_message(form);
|
103
|
-
print_results(message_box, response.messages);
|
104
|
-
$(form).trigger("failed", [xhr, status, errorThrown]);
|
105
|
-
$(message_box).addClass('ko');
|
106
|
-
add_errors(form, response.errors);
|
107
|
-
|
108
|
-
},
|
109
|
-
complete: function () {
|
110
|
-
if (!recaptha_disabled) {
|
111
|
-
grecaptcha.reset();
|
75
|
+
if ($(form).find("[name='_dontcare']").length < 1) {
|
76
|
+
$(form).append("<input type=\"hidden\" name=\"_dontcare\">")
|
112
77
|
}
|
113
|
-
$(form).find("input[type='submit']").attr("disabled", false);
|
114
|
-
hide_loader(form);
|
115
|
-
}
|
116
|
-
});
|
117
|
-
}
|
118
78
|
|
119
|
-
|
79
|
+
var formdata = new FormData($(form)[0])
|
80
|
+
$.ajax({
|
81
|
+
method: method,
|
82
|
+
url: action,
|
83
|
+
dataType: "json",
|
84
|
+
data: formdata,
|
85
|
+
contentType: false,
|
86
|
+
processData: false,
|
87
|
+
success: function (response, status, xhr) {
|
88
|
+
|
89
|
+
clear_deafult_message(form);
|
90
|
+
var message_box = $(form).parent().find(".messages")[0];
|
91
|
+
if (!$(message_box).find(".custom_message").length > 0) {
|
92
|
+
print_results(message_box, response.messages);
|
93
|
+
}
|
94
|
+
|
95
|
+
$(form).trigger("successed");
|
96
|
+
$(message_box).addClass('ok');
|
97
|
+
$(form).trigger("reset");
|
98
|
+
|
99
|
+
},
|
100
|
+
error: function (xhr, status, errorThrown) {
|
101
|
+
var response = JSON.parse(xhr.responseText);
|
102
|
+
var message_box = $(form).parent().find(".messages")[0];
|
103
|
+
clear_deafult_message(form);
|
104
|
+
print_results(message_box, response.messages);
|
105
|
+
$(form).trigger("failed", [xhr, status, errorThrown]);
|
106
|
+
$(message_box).addClass('ko');
|
107
|
+
add_errors(form, response.errors);
|
108
|
+
|
109
|
+
},
|
110
|
+
complete: function () {
|
111
|
+
if (!recaptha_disabled) {
|
112
|
+
grecaptcha.reset();
|
113
|
+
}
|
114
|
+
$(form).find("input[type='submit']").attr("disabled", false);
|
115
|
+
hide_loader(form);
|
116
|
+
}
|
117
|
+
});
|
118
|
+
}
|
119
|
+
|
120
|
+
load_invisible_recaptcha = function () {
|
120
121
|
|
121
122
|
|
122
|
-
|
123
|
+
$(document).ready(function () {
|
123
124
|
|
124
125
|
|
126
|
+
$(".ajax_forms").each(function (element, index) {
|
125
127
|
|
126
|
-
|
128
|
+
var form = this;
|
129
|
+
var action = $(this).attr("action");
|
130
|
+
var method = $(this).attr("method");
|
131
|
+
var submit_button = $(form).find(".submit").last();
|
127
132
|
|
128
|
-
var form = this;
|
129
|
-
var action = $(this).attr("action");
|
130
|
-
var method = $(this).attr("method");
|
131
|
-
var submit_button = $(form).find(".submit").last();
|
132
133
|
|
134
|
+
if (!recaptha_disabled) {
|
135
|
+
var container_div = $('<div class="invisible_recaptcha_container"></div>');
|
136
|
+
$(submit_button).before(container_div);
|
133
137
|
|
134
|
-
|
135
|
-
|
136
|
-
|
138
|
+
var widget_id = grecaptcha.render(container_div[0], {
|
139
|
+
'sitekey': '<%= Recaptcha.configuration.site_key! %>',
|
140
|
+
'badge': 'inline',
|
141
|
+
'size': 'invisible',
|
142
|
+
'callback': function () {
|
143
|
+
submit_function(form, method, action);
|
144
|
+
}
|
145
|
+
});
|
137
146
|
|
138
|
-
|
139
|
-
'sitekey': '<%= Recaptcha.configuration.site_key! %>',
|
140
|
-
'badge': 'inline',
|
141
|
-
'size': 'invisible',
|
142
|
-
'callback': function () {
|
143
|
-
submit_function(form, method, action);
|
144
|
-
}
|
145
|
-
});
|
147
|
+
$(form).data("widget-id", widget_id)
|
146
148
|
|
147
|
-
|
149
|
+
}
|
148
150
|
|
149
|
-
|
151
|
+
$(form).children('.ajax_forms_loader').fadeOut();
|
152
|
+
});
|
150
153
|
|
151
|
-
|
152
|
-
});
|
154
|
+
$(document).on("submit", ".ajax_forms", function (event) {
|
153
155
|
|
154
|
-
|
156
|
+
event.preventDefault ? event.preventDefault() : (event.returnValue = false);
|
155
157
|
|
156
|
-
|
158
|
+
var form = $(event.target);//.closest("form");
|
159
|
+
var action = $(form).attr("action");
|
160
|
+
var method = $(form).attr("method");
|
161
|
+
var widget_id = $(form).data("widget-id");
|
162
|
+
// disable all submit buttons
|
163
|
+
$(form).find("input[type='submit']").attr("disabled", true)
|
157
164
|
|
158
|
-
var form = $(event.target);//.closest("form");
|
159
|
-
var action = $(form).attr("action");
|
160
|
-
var method = $(form).attr("method");
|
161
|
-
var widget_id = $(form).data("widget-id");
|
162
|
-
// disable all submit buttons
|
163
|
-
$(form).find("input[type='submit']").attr("disabled", true)
|
164
165
|
|
166
|
+
if (!recaptha_disabled) {
|
167
|
+
grecaptcha.execute(widget_id)
|
168
|
+
} else {
|
169
|
+
submit_function(form, method, action);
|
170
|
+
}
|
165
171
|
|
166
|
-
if (!recaptha_disabled) {
|
167
|
-
grecaptcha.execute(widget_id)
|
168
|
-
} else {
|
169
|
-
submit_function(form, method, action);
|
170
|
-
}
|
171
172
|
|
173
|
+
});
|
172
174
|
|
173
|
-
|
175
|
+
$('.ajax_forms').on('change', '.invalid', function () {
|
176
|
+
$(this).removeClass("invalid");
|
177
|
+
$(this).parent().children('.error-message').hide();
|
178
|
+
});
|
174
179
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
});
|
180
|
+
});
|
181
|
+
};
|
182
|
+
$.getScript("//www.google.com/recaptcha/api.js?onload=load_invisible_recaptcha&render=explicit")
|
179
183
|
|
180
|
-
});
|
181
|
-
};
|
182
|
-
$.getScript("//www.google.com/recaptcha/api.js?onload=load_invisible_recaptcha&render=explicit")
|
183
184
|
|
185
|
+
})(jQuery);
|
184
186
|
|
185
|
-
})(jQuery);
|
@@ -0,0 +1,21 @@
|
|
1
|
+
.ajax_forms {
|
2
|
+
.alchemy-dialog {
|
3
|
+
max-width: 1080px !important;
|
4
|
+
width: 100% !important;
|
5
|
+
|
6
|
+
.attribute_row {
|
7
|
+
margin: 10px 0px;
|
8
|
+
|
9
|
+
.labelfield {
|
10
|
+
font-size: 20px;
|
11
|
+
font-weight: bold;
|
12
|
+
text-transform: uppercase;
|
13
|
+
}
|
14
|
+
|
15
|
+
.valuefield {
|
16
|
+
font-size: 15px;
|
17
|
+
}
|
18
|
+
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
.ajax_forms {
|
2
|
+
position: relative;
|
3
|
+
|
4
|
+
.ajax_forms_loader {
|
5
|
+
position: absolute;
|
6
|
+
left: 0;
|
7
|
+
display: flex;
|
8
|
+
top: 0;
|
9
|
+
align-items: center;
|
10
|
+
justify-content: center;
|
11
|
+
height: 100%;
|
12
|
+
width: 100%;
|
13
|
+
font-size: 2rem;
|
14
|
+
background: rgba(255, 255, 255, 0.5);
|
15
|
+
}
|
16
|
+
|
17
|
+
}
|
@@ -34,6 +34,9 @@ module Alchemy
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def show
|
38
|
+
end
|
39
|
+
|
37
40
|
protected
|
38
41
|
|
39
42
|
def common_search_filter_includes
|
@@ -47,6 +50,11 @@ module Alchemy
|
|
47
50
|
].freeze
|
48
51
|
end
|
49
52
|
|
53
|
+
def load_resource
|
54
|
+
@resource = resource_handler.model.find(params[:id])
|
55
|
+
instance_variable_set("@#{resource_handler.resource_name}", @resource)
|
56
|
+
end
|
57
|
+
|
50
58
|
|
51
59
|
end
|
52
60
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Alchemy
|
2
|
+
module Admin
|
3
|
+
module AjaxFormsHelper
|
4
|
+
|
5
|
+
def alchemy_body_class
|
6
|
+
[
|
7
|
+
"ajax_forms",
|
8
|
+
controller_name,
|
9
|
+
action_name,
|
10
|
+
content_for(:main_menu_style),
|
11
|
+
content_for(:alchemy_body_class)
|
12
|
+
].compact
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<tr class="<%= cycle('even', 'odd') %>">
|
2
|
+
<% resource_handler.attributes.each do |attribute| %>
|
3
|
+
<td class="<%= attribute[:type] %> <%= attribute[:name] %>">
|
4
|
+
<% if attribute[:type] == :boolean %>
|
5
|
+
<%= resource.public_send(attribute[:name]) ? render_icon(:check) : nil %>
|
6
|
+
<% else %>
|
7
|
+
<%= render_attribute(resource, attribute) %>
|
8
|
+
<% end %>
|
9
|
+
</td>
|
10
|
+
<% end %>
|
11
|
+
<td class="tools">
|
12
|
+
<% if can?(:destroy, resource) %>
|
13
|
+
<%= delete_button resource_path(resource, search_filter_params) %>
|
14
|
+
<% end %>
|
15
|
+
<% if can?(:edit, resource) %>
|
16
|
+
<%= link_to_dialog render_icon(:edit),
|
17
|
+
edit_resource_path(resource, search_filter_params),
|
18
|
+
{
|
19
|
+
title: Alchemy.t('Edit'),
|
20
|
+
size: resource_window_size
|
21
|
+
},
|
22
|
+
title: Alchemy.t('Edit') %>
|
23
|
+
<% end %>
|
24
|
+
<% if can?(:show, resource) %>
|
25
|
+
<%= link_to_dialog render_icon(:eye),
|
26
|
+
resource_path(resource, search_filter_params),
|
27
|
+
{
|
28
|
+
title: Alchemy.t('Show'),
|
29
|
+
size: resource_window_size,
|
30
|
+
class: "pippo"
|
31
|
+
},
|
32
|
+
title: Alchemy.t('Show')
|
33
|
+
%>
|
34
|
+
<% end %>
|
35
|
+
</td>
|
36
|
+
</tr>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
<% resource_handler.attributes.each do |attribute| %>
|
3
|
+
<div class="attribute_row">
|
4
|
+
<div class="labelfield">
|
5
|
+
<%= @resource.class.human_attribute_name(attribute[:name].to_sym) %>
|
6
|
+
</div>
|
7
|
+
<div class="valuefield">
|
8
|
+
<%= render_attribute(@resource, attribute) %>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<% end %>
|
data/config/locales/it.yml
CHANGED
@@ -154,12 +154,26 @@ resources :#{name.underscore.pluralize} , only: [:create]\n
|
|
154
154
|
inject_into_file "app/assets/javascripts/application.js" , before: '//= require_tree .' do
|
155
155
|
"\n//= require jquery3\n//= require ajax_forms\n"
|
156
156
|
end
|
157
|
+
inject_into_file "vendor/assets/stylesheets/alchemy/admin/all.css" , before: '*= require_tree .' do
|
158
|
+
"\n*= require alchemy/ajax/form/backend/custom_resource_show\n"
|
159
|
+
end
|
160
|
+
# inject_into_file "app/assets/stylesheets/application.css", {} do
|
161
|
+
# "\n*= require alchemy/ajax/form/style\n"
|
162
|
+
# end
|
163
|
+
# inject_into_file "app/assets/stylesheets/application.*" , before: '*= require alchemy/ajax/form/style' do
|
164
|
+
# "\n*= require @fortawesome/fontawesome-free/css/all\n"
|
165
|
+
# end
|
166
|
+
|
167
|
+
# inject_into_file "app/assets/javascripts/application.js", {} do
|
168
|
+
# "\n//= @fortawesome/fontawesome-free/js/all\n"
|
169
|
+
# end
|
157
170
|
end
|
158
171
|
|
159
172
|
desc "Run migration and install mjml and create alchemy elements"
|
160
173
|
def run_scripts
|
161
174
|
rake("db:migrate")
|
162
175
|
#run "npm install mjml"
|
176
|
+
run "yarn add @fortawesome/fontawesome-free"
|
163
177
|
generate("alchemy:elements","--skip")
|
164
178
|
end
|
165
179
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alchemy-ajax-form
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alessandro Baccanelli
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -122,12 +122,17 @@ files:
|
|
122
122
|
- app/assets/images/ajax_form.png
|
123
123
|
- app/assets/images/alchemy/ajax_form.png
|
124
124
|
- app/assets/javascripts/ajax_forms.js.erb
|
125
|
+
- app/assets/stylesheets/alchemy/ajax/form/backend/custom_resource_show.scss
|
126
|
+
- app/assets/stylesheets/alchemy/ajax/form/style.scss
|
125
127
|
- app/controllers/alchemy/admin/ajax_forms_controller.rb
|
126
128
|
- app/controllers/alchemy/ajax_forms_controller.rb
|
129
|
+
- app/helpers/alchemy/admin/ajax_forms_helper.rb
|
127
130
|
- app/mailers/alchemy/ajax_forms_mailer.rb
|
128
131
|
- app/models/alchemy/ajax_form.rb
|
129
132
|
- app/models/alchemy/ajax_form_ability.rb
|
133
|
+
- app/views/alchemy/admin/ajax_forms/_resource.html.erb
|
130
134
|
- app/views/alchemy/admin/ajax_forms/_table.html.erb
|
135
|
+
- app/views/alchemy/admin/ajax_forms/show.html.erb
|
131
136
|
- app/views/alchemy/ajax_forms/create.json.jbuilder
|
132
137
|
- app/views/alchemy/ajax_forms_mailer/mjml_notify_message.mjml.erb
|
133
138
|
- app/views/alchemy/ajax_forms_mailer/mjml_notify_user_message.mjml.erb
|