Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11,440 changes: 11,440 additions & 0 deletions app/assets/javascripts/quill.js

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions app/assets/stylesheets/quill-snow.css

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ def question_type_javascript_params(question)
"form.querySelector(\"##{question.ui_selector}\") && form.querySelector(\"##{question.ui_selector}\").dataset.rawValue"
elsif question.question_type == 'textarea'
"form.querySelector(\"##{question.ui_selector}\") && form.querySelector(\"##{question.ui_selector}\").value"
elsif question.question_type == 'rich_textarea'
"form.querySelector(\"##{question.ui_selector}\") && form.querySelector(\"##{question.ui_selector}\").value"
elsif question.question_type == 'radio_buttons'
"form.querySelector(\"input[name=#{question.ui_selector}]:checked\") && form.querySelector(\"input[name=#{question.ui_selector}]:checked\").value"
elsif question.question_type == 'star_radio_buttons'
Expand Down
4 changes: 4 additions & 0 deletions app/models/form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ def check_expired
end
end

def has_rich_text_questions?
questions.where(question_type: "rich_textarea").exists?
end

def self.archive_expired!
Form.where("expiration_date is not null and expiration_date < NOW() and aasm_state <> 'archived'").find_each do |form|
@event = Event.log_event(Event.names[:form_archived], 'Form', form.uuid, "Form #{form.name} archived at #{DateTime.now}", 1)
Expand Down
1 change: 1 addition & 0 deletions app/models/question.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Question < ApplicationRecord
'dropdown',
'combobox',
# Custom elements
'rich_textarea',
'text_display',
'custom_text_display',
'states_dropdown',
Expand Down
4 changes: 4 additions & 0 deletions app/views/admin/forms/example.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
<!--- End demo content -->
<script src="<%= touchpoint_url(@form.short_uuid, format: :js) %>" async></script>
<script>
document.addEventListener('onTouchpointsFormLoaded', function() {
console.log("fired onTouchpointsFormLoaded")
});

document.addEventListener('onTouchpointsModalClose', function() {
console.log("fired onTouchpointsModalClose")
});
Expand Down
2 changes: 2 additions & 0 deletions app/views/admin/questions/_question.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<% @form_component_path = 'components/forms/edit/question_types/combobox' %>
<% elsif question.question_type == "textarea" %>
<% @form_component_path = 'components/forms/edit/question_types/text_area' %>
<% elsif question.question_type == "rich_textarea" %>
<% @form_component_path = 'components/forms/edit/question_types/rich_textarea' %>
<% elsif question.question_type == "hidden_field" %>
<% @form_component_path = 'components/forms/edit/question_types/hidden_field' %>
<% elsif question.question_type == "custom_text_display" %>
Expand Down
2 changes: 2 additions & 0 deletions app/views/components/forms/_custom.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@
<%= render 'components/forms/question_types/combobox', form: form, question: question, question_number: multi_section_question_number %>
<% elsif question.question_type == "textarea" %>
<%= render 'components/forms/question_types/text_area', form: form, question: question, question_number: multi_section_question_number %>
<% elsif question.question_type == "rich_textarea" %>
<%= render 'components/forms/question_types/rich_textarea', form: form, question: question, question_number: multi_section_question_number %>
<% elsif question.question_type == "text_display" %>
<%= render 'components/forms/question_types/text_display', form: form, question: question, question_number: multi_section_question_number %>
<% elsif question.question_type == "date_select" %>
Expand Down
23 changes: 22 additions & 1 deletion app/views/components/forms/edit/_builder.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<% @instruction_text_limit = 1500 %>
<% @disclaimer_text_limit = 1000 %>

<%= javascript_include_tag "quill.js" %>
<%= stylesheet_link_tag "quill-snow.css" %>
<%= render 'admin/forms/logo_display', { form: form } %>
<hr
class="margin-top-3 margin-bottom-3"
Expand Down Expand Up @@ -376,4 +377,24 @@ function textCounter(field,maxlimit) {
countfield.innerText = "" + (maxlimit - field.value.length) + " <%= t :characters_left %>";
}
}

document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".quill").forEach((wrapper) => {
const editorContainer = wrapper.querySelector(".editor");
const hiddenInput = wrapper.querySelector("input[type=hidden]");

if (editorContainer && hiddenInput) {
const quill = new Quill(editorContainer, {
theme: 'snow',
placeholder: 'Write something...',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }]
]
}
});
}
});
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<%
form_options = {
class: "usa-textarea",
required: question.is_required,
maxlength: question.max_length,
}

if question.help_text.present?
form_options.merge!({
"aria-describedby" => "question-id-#{question.id}-help-text"
})
end
%>

<div class="usa-form-group quill">
<%= render 'components/question_title_label', question: question %>
<input
type="hidden"
name="my_model[rich_text]"
id="<%= question.ui_selector %>">
<div
id="quill-editor-<%= question.ui_selector %>"
class="editor"
style="min-height: 100px;"></div>
</div>
25 changes: 25 additions & 0 deletions app/views/components/forms/question_types/_rich_textarea.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<%
form_options = {
class: "usa-textarea",
required: question.is_required,
maxlength: question.max_length,
}

if question.help_text.present?
form_options.merge!({
"aria-describedby" => "question-id-#{question.id}-help-text"
})
end
%>

<div class="usa-form-group quill">
<%= render 'components/question_title_label', question: question %>
<input
type="hidden"
name="my_model[rich_text]"
id="<%= question.ui_selector %>">
<div
id="quill-editor-<%= question.ui_selector %>"
class="editor"
style="min-height: 100px;"></div>
</div>
47 changes: 45 additions & 2 deletions app/views/components/widget/_fba.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ function FBAform(d, N) {
},
init: function(options) {
this.javascriptIsEnabled();
<%- if form.has_rich_text_questions? %>
this.loadQuill();
<% end %>
this.options = options;
if (this.options.loadCSS) {
this._loadCss();
Expand Down Expand Up @@ -121,7 +124,8 @@ function FBAform(d, N) {
if (customButtonEl != null) {
customButtonEl.setAttribute('data-open-modal', '');
customButtonEl.setAttribute('aria-controls', this.modalId());
customButtonEl.addEventListener('click', () => d.dispatchEvent(new Event('onTouchpointsModalOpen')));
customButtonEl.addEventListener('click', () => d.dispatchEvent(new CustomEvent('onTouchpointsModalOpen', { detail: { form: this } }
)));
}
}
}
Expand Down Expand Up @@ -387,7 +391,7 @@ function FBAform(d, N) {
this.buttonEl.setAttribute('aria-controls', this.modalId());
this.buttonEl.setAttribute('data-open-modal', '');
this.buttonEl.innerHTML = this.options.modalButtonText;
this.buttonEl.addEventListener('click', () => d.dispatchEvent(new Event('onTouchpointsModalOpen')));
this.buttonEl.addEventListener('click', () => d.dispatchEvent(new CustomEvent('onTouchpointsModalOpen', { detail: { form: this } } )));
this.landmarkElement.appendChild(this.buttonEl);
d.body.appendChild(this.landmarkElement);

Expand Down Expand Up @@ -650,6 +654,45 @@ function FBAform(d, N) {
modalId: function() {
return `fba-modal-${this.options.formId}`;
},
modalElement: function() {
return document.getElementById(this.modalId());
},
<%- if form.has_rich_text_questions? %>
loadQuill: function() {
let script = document.createElement("script");
script.src = "<%= asset_path('quill.js') %>";
script.async = true;
script.defer = true;
document.head.appendChild(script);
setTimeout(this.initQuill, 1000); // TODO: consider using something else besides setTimeout
},
initQuill: function() {
// Loop all .quill form groups and initialize Quill
document.querySelectorAll(".quill").forEach((wrapper) => {
const editorContainer = wrapper.querySelector(".editor");
const hiddenInput = wrapper.querySelector("input[type=hidden]");

if (editorContainer && hiddenInput) {
const quill = new Quill(editorContainer, {
theme: 'snow',
placeholder: 'Write something...',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ 'list': 'ordered'}, { 'list': 'bullet' }]
]
}
});

// Sync to hidden field on change
quill.on('text-change', function () {
hiddenInput.value = quill.root.innerHTML;
});
}

});
},
<% end %>
<%- if form.enable_turnstile? %>
loadTurnstile: function() {
let script = document.createElement("script");
Expand Down
4 changes: 4 additions & 0 deletions app/views/components/widget/_widget.css.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1076,3 +1076,7 @@
.fba-modal-dialog .touchpoints-form-wrapper .usa-form {
max-width: 100%;
}

<%- if form.has_rich_text_questions? %>
<%= File.read(Rails.root.join("app/assets/stylesheets/quill-snow.css")) %>
<% end %>
28 changes: 19 additions & 9 deletions db/seeds/forms/kitchen_sink.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ def self.kitchen_sink
answer_field: :answer_03,
is_required: false,
})
Question.create!({
form: custom_form,
form_section: text_elements_section,
text: "Rich Text Area",
question_type: "rich_textarea",
help_text: "This is help text for a rich textarea.",
position: 4,
answer_field: :answer_04,
is_required: false,
})

option_elements_section = custom_form.form_sections.create(title: "Option elements", position: 2)
radio_button_question = Question.create!({
Expand All @@ -57,8 +67,8 @@ def self.kitchen_sink
text: "Custom Question Radio Buttons",
question_type: "radio_buttons",
help_text: "This is help text for radio buttons.",
position: 4,
answer_field: :answer_04,
position: 5,
answer_field: :answer_05,
is_required: true,
})
QuestionOption.create!({
Expand Down Expand Up @@ -86,8 +96,8 @@ def self.kitchen_sink
text: "Custom Question Checkboxes",
question_type: "checkbox",
help_text: "This is help text for checkboxes.",
position: 5,
answer_field: :answer_05,
position: 6,
answer_field: :answer_06,
is_required: false,
})
QuestionOption.create!({
Expand Down Expand Up @@ -121,8 +131,8 @@ def self.kitchen_sink
text: "Custom Question Dropdown",
question_type: "dropdown",
help_text: "This is help text for a dropdown.",
position: 6,
answer_field: :answer_06,
position: 7,
answer_field: :answer_07,
is_required: false,
})
QuestionOption.create!({
Expand Down Expand Up @@ -150,7 +160,7 @@ def self.kitchen_sink
form_section: custom_elements_section,
text: '<p>Custom text <a href="#">that supports HTML</a> goes here.</p>',
question_type: "text_display",
position: 7,
position: 8,
answer_field: :answer_15,
is_required: false,
})
Expand All @@ -160,7 +170,7 @@ def self.kitchen_sink
form_section: custom_elements_section,
text: "Custom text display",
question_type: "custom_text_display",
position: 8,
position: 9,
answer_field: :answer_16,
is_required: false
})
Expand All @@ -170,7 +180,7 @@ def self.kitchen_sink
form_section: custom_elements_section,
text: "Star radio buttons",
question_type: "star_radio_buttons",
position: 9,
position: 10,
answer_field: :answer_17,
is_required: false
})
Expand Down