From 47c2358866676b31a390c9bd407db313299aac33 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Wed, 1 Apr 2020 12:41:20 -0400 Subject: [PATCH 01/16] Add Request Type to agency search form --- app/request/forms.py | 12 +++++++++++- app/templates/request/_agency_search_form.html | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/request/forms.py b/app/request/forms.py index 0b53f0aab..7d4bea119 100644 --- a/app/request/forms.py +++ b/app/request/forms.py @@ -28,7 +28,7 @@ response_type, ) from app.lib.db_utils import get_agency_choices -from app.models import Reasons, LetterTemplates, EnvelopeTemplates +from app.models import Reasons, LetterTemplates, EnvelopeTemplates, CustomRequestForms class PublicUserRequestForm(Form): @@ -437,6 +437,7 @@ class SearchRequestsForm(Form): # TODO: Add class docstring agency_ein = SelectField("Agency") agency_user = SelectField("User") + request_type = SelectField("Request Type", choices=[]) # category = SelectField('Category', get_categories()) @@ -487,6 +488,15 @@ def __init__(self): ] self.agency_user.default = current_user.get_id() + if default_agency.agency_features["custom_request_forms"]["enabled"]: + self.request_type.choices = [ + (custom_request_form.form_name, custom_request_form.form_name) + for custom_request_form in CustomRequestForms.query.filter_by( + agency_ein=default_agency.ein + ).all() + ] + self.request_type.choices.insert(0, ("", "All")) + # process form for default values self.process() diff --git a/app/templates/request/_agency_search_form.html b/app/templates/request/_agency_search_form.html index 644b29e21..2f3131576 100644 --- a/app/templates/request/_agency_search_form.html +++ b/app/templates/request/_agency_search_form.html @@ -221,6 +221,12 @@ {% endif %} +
+
+ + {{ form.request_type(class="input-block-level") }} +
+
From 229e80574788bd7a10370ed8c1217ace0c29cf9a Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Wed, 1 Apr 2020 16:57:13 -0400 Subject: [PATCH 02/16] Add request_type to elasticsearch and frontend --- app/models.py | 1 + app/search/utils.py | 14 ++++++++++++++ app/search/views.py | 2 ++ app/static/js/request/search.js | 6 ++++++ app/templates/request/_agency_search_form.html | 12 +++++++----- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/models.py b/app/models.py index 20a78469d..2555346d5 100644 --- a/app/models.py +++ b/app/models.py @@ -1018,6 +1018,7 @@ def es_create(self): ), "requester_name": self.requester.name, "public_title": "Private" if self.privacy["title"] else self.title, + "request_type": [metadata["form_name"] for metadata in self.custom_metadata.values()], }, ) diff --git a/app/search/utils.py b/app/search/utils.py index 4ef25b6b8..ce5573adb 100644 --- a/app/search/utils.py +++ b/app/search/utils.py @@ -107,6 +107,7 @@ def create_index(): "format": "strict_date_hour_minute_second", }, "assigned_users": {"type": "keyword"}, + "request_type": {"type": "keyword"}, } } } @@ -157,6 +158,9 @@ def create_docs(): "public_title": "Private" if not r.privacy["title"] else r.title, "assigned_users": [ "{guid}".format(guid=user.guid) for user in r.agency_users + ], + "request_type": [ + metadata["form_name"] for metadata in r.custom_metadata.values() ] # public_agency_request_summary } @@ -195,6 +199,7 @@ def search_requests( date_closed_to, agency_ein, agency_user_guid, + request_type, open_, closed, in_progress, @@ -231,6 +236,7 @@ def search_requests( :param date_closed_to: date closed to :param agency_ein: agency ein to filter by :param agency_user_guid: user (agency) guid to filter by + :param request_type: request type to filter by :param open_: filter by opened requests? :param closed: filter by closed requests? :param in_progress: filter by in-progress requests? @@ -368,6 +374,7 @@ def datestr_local_to_utc(datestr): date_ranges, agency_ein, agency_user_guid, + request_type, match_type, ) if foil_id: @@ -429,6 +436,7 @@ def datestr_local_to_utc(datestr): "agency_request_summary", "description", "assigned_users", + "request_type", ], size=result_set_size, from_=start, @@ -460,6 +468,7 @@ def datestr_local_to_utc(datestr): "agency_request_summary", "description", "assigned_users", + "request_type" ], size=result_set_size, from_=start, @@ -496,6 +505,7 @@ def __init__( date_ranges, agency_ein, agency_user_guid, + request_type, match_type, ): self.__query = query @@ -513,6 +523,10 @@ def __init__( self.__default_filters.append( {"term": {"assigned_users": agency_user_guid}} ) + if request_type: + self.__default_filters.append( + {"term": {"request_type": request_type}} + ) self.__filters = [] self.__conditions = [] diff --git a/app/search/views.py b/app/search/views.py index e74fb908c..9ecdaaf39 100644 --- a/app/search/views.py +++ b/app/search/views.py @@ -99,6 +99,7 @@ def requests(): date_closed_to=request.args.get("date_closed_to"), agency_ein=agency_ein, agency_user_guid=request.args.get("agency_user"), + request_type=request.args.get("request_type"), open_=eval_request_bool(request.args.get("open")), closed=eval_request_bool(request.args.get("closed")), in_progress=eval_request_bool(request.args.get("in_progress")) @@ -215,6 +216,7 @@ def requests_doc(doc_type): date_closed_to=request.args.get("date_closed_to"), agency_ein=agency_ein, agency_user_guid=request.args.get("agency_user"), + request_type=request.args.get("request_type"), open_=eval_request_bool(request.args.get("open")), closed=eval_request_bool(request.args.get("closed")), in_progress=eval_request_bool(request.args.get("in_progress")) diff --git a/app/static/js/request/search.js b/app/static/js/request/search.js index ae6ff6e3c..0f9e5921c 100644 --- a/app/static/js/request/search.js +++ b/app/static/js/request/search.js @@ -21,6 +21,7 @@ $(function () { agencySelect = $("#agency_ein"), agencyUserDiv = $("#agency-user-div"), agencyUserSelect = $("#agency_user"), + requestType = $("#request_type"), resultsHeader = "resultsHeading", isAgencyUser = ($("#is-agency-user").val() === "true"), resultcol = []; @@ -419,6 +420,7 @@ $(function () { agencySelect.change(function () { agencyUserSelect.empty(); + requestType.empty(); if (agencySelect.val()) { $.ajax({ @@ -455,6 +457,10 @@ $(function () { resetAndSearch(); }); + requestType.change(function () { + resetAndSearch(); + }); + next.click(function (event) { toFocus = true; event.preventDefault(); diff --git a/app/templates/request/_agency_search_form.html b/app/templates/request/_agency_search_form.html index 2f3131576..2657b1a0d 100644 --- a/app/templates/request/_agency_search_form.html +++ b/app/templates/request/_agency_search_form.html @@ -221,12 +221,14 @@
{% endif %} -
-
- - {{ form.request_type(class="input-block-level") }} + {% if current_user.is_agency_active() %} +
+
+ + {{ form.request_type(class="input-block-level") }} +
-
+ {% endif %}
From f112ad5f625fb5eec5fc1e12dd272687b09454d0 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Wed, 1 Apr 2020 16:57:52 -0400 Subject: [PATCH 03/16] WIP - Create API to get request type for agency --- app/agency/api/views.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/agency/api/views.py b/app/agency/api/views.py index eab6cf1e3..1353cabdb 100644 --- a/app/agency/api/views.py +++ b/app/agency/api/views.py @@ -161,3 +161,15 @@ def get_custom_request_form_fields(): data['tooltips'] = tooltips data['error_messages'] = error_messages return jsonify(data), 200 + + +@agency_api_blueprint.route('/request_types', methods=['GET']) +def get_request_types(agency_ein): + """ + Retrieve the request types (custom request form names) for the specified agency. + + :param agency_ein: Agency EIN (String) + + :return: JSON Object({"request_type": [('', 'All'), ('Form Name', 'Form Name')]}), 200 + """ + pass From 53aa423019590e101d865abf4785c838f6c0df32 Mon Sep 17 00:00:00 2001 From: Gary Zhou Date: Thu, 2 Apr 2020 15:26:48 -0400 Subject: [PATCH 04/16] Complete API and ajax call for getting request_type by agency for search --- app/agency/api/views.py | 18 +++++++++++++++--- app/static/js/request/search.js | 17 +++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/agency/api/views.py b/app/agency/api/views.py index 1353cabdb..d6a57baf9 100644 --- a/app/agency/api/views.py +++ b/app/agency/api/views.py @@ -13,7 +13,7 @@ get_letter_templates, get_reasons ) -from app.models import CustomRequestForms +from app.models import Agencies, CustomRequestForms import json from sqlalchemy import asc @@ -163,7 +163,7 @@ def get_custom_request_form_fields(): return jsonify(data), 200 -@agency_api_blueprint.route('/request_types', methods=['GET']) +@agency_api_blueprint.route('/request_types/', methods=['GET']) def get_request_types(agency_ein): """ Retrieve the request types (custom request form names) for the specified agency. @@ -172,4 +172,16 @@ def get_request_types(agency_ein): :return: JSON Object({"request_type": [('', 'All'), ('Form Name', 'Form Name')]}), 200 """ - pass + if current_user.is_agency_active(agency_ein): + agency = Agencies.query.filter_by(ein=agency_ein).one_or_none() + if agency is not None and agency.agency_features['custom_request_forms']['enabled']: + request_types = [ + (custom_request_form.form_name, custom_request_form.form_name) + for custom_request_form in CustomRequestForms.query.filter_by( + agency_ein=agency_ein + ).all() + ] + request_types.insert(0, ("", "All")) + return jsonify({"request_types": request_types}), 200 + + return jsonify({}), 404 diff --git a/app/static/js/request/search.js b/app/static/js/request/search.js index 0f9e5921c..6581b41ee 100644 --- a/app/static/js/request/search.js +++ b/app/static/js/request/search.js @@ -442,12 +442,21 @@ $(function () { } agencyUserDiv.show(); - }, - // search after ajax call is complete so default value for standard user is selected - complete: function () { - resetAndSearch(); } }); + + $.ajax({ + url: "/agency/api/v1.0/request_types/" + agencySelect.val(), + success: function (data) { + // Populate request types + $.each(data.request_types, function() { + requestType.append($("
diff --git a/app/templates/response/add_extension.js.html b/app/templates/response/add_extension.js.html index e116c3a46..1e3a1af24 100644 --- a/app/templates/response/add_extension.js.html +++ b/app/templates/response/add_extension.js.html @@ -26,6 +26,41 @@ var generate_letters_enabled = form.find("#generate-letters-enabled").val(); + tinymce.init({ + menubar: false, + // sets tinymce to enable only on specific textareas classes + mode: "specific_textareas", + // selector for tinymce textarea classes is set to 'tinymce-area' + editor_selector: "tinymce-extension-reason", + elementpath: false, + convert_urls: false, + height: 250, + plugins: ["noneditable", "preventdelete", "lists"], + toolbar: ['undo redo | formatselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent add_check'], + forced_root_block: '', + setup: function (editor) { + editor.ui.registry.addButton('add_check', { + text: 'Add ✔', + onAction: function () { + editor.insertContent(' ✔ '); + } + }); + + editor.on('keyup', function () { + let currentLength = tinyMCE.get('extension-reason').getContent({format: 'text'}).trim().length; + characterCounter("#extension-reason-character-count", 5000, currentLength, 20); + if (currentLength > 5000) { + next1.prop('disabled', true); + $('#extension-reason-maxlength-error').show(); + } + else { + next1.prop('disabled', false); + $('#extension-reason-maxlength-error').hide(); + } + }); + } + }); + // parsley var required = [letter_info, method]; for (var i = 0; i < required.length; i++) { @@ -66,6 +101,8 @@ // Handles click events on the second next button $(next2).click(function (e) { + tinyMCE.triggerSave(); + if (method.val() === 'emails') { if ($("#custom-extension").is(':visible')) { $("#custom-extension").parsley().validate(); @@ -91,7 +128,7 @@ // Store the values of from input fields into variables var length = $('#extension-select').val(); var custom_due_date = $('#custom-extension').val(); - var reason = $('#extension-reason').val(); + var reason = tinyMCE.get('extension-reason').getContent(); $.ajax({ url: "/response/email", @@ -304,10 +341,6 @@ $("#extension-reason").attr("data-parsley-minlength", "20"); $("#extension-reason").attr("data-parsley-maxlength", "5000"); - // Apply parsley minimum length validation to extension reason - $("#extension-reason").attr("data-parsley-minlength","20"); - $("#extension-reason").attr("data-parsley-maxlength","5000"); - // Apply custom validation messages method.attr("data-parsley-required-message", " " + @@ -333,10 +366,8 @@ $("#extension-reason").attr("data-parsley-minlength-message", " " + "Error, extension reason must be at least 20 characters."); - - // Set character counter for extension reason - $("#extension-reason").keyup(function () { - characterCounter("#extension-reason-character-count", 5000, $(this).val().length) - }); + $("#extension-reason").attr("data-parsley-maxlength-message", + " " + + "Error, extension reason must be less than 5000 characters."); }); \ No newline at end of file From 078c310260bf42caea365907409db4cab0510e56 Mon Sep 17 00:00:00 2001 From: johnyu95 Date: Wed, 29 Apr 2020 12:41:56 -0400 Subject: [PATCH 14/16] Updated acknowledgements to use tinymce and code cleanup --- .../js/request/view-request-responses.js | 38 ++++++------- app/static/styles/view_request.css | 6 +++ .../email_response_acknowledgment.html | 16 +++--- .../email_response_instruction.html | 13 ++--- .../email_templates/email_response_note.html | 13 ++--- .../responses/modal_body/determinations.html | 4 +- .../responses/modal_body/instructions.html | 15 ++---- .../request/responses/modal_body/notes.html | 8 +-- app/templates/request/view_request.js.html | 2 +- .../response/add_acknowledgment.html | 11 ++-- .../response/add_acknowledgment.js.html | 53 +++++++++++++++---- app/templates/response/add_extension.html | 4 +- app/templates/response/add_extension.js.html | 10 ++-- app/templates/response/add_instruction.html | 6 +-- .../response/add_instruction.js.html | 19 ++++--- app/templates/response/add_note.html | 4 +- app/templates/response/add_note.js.html | 10 ++-- 17 files changed, 140 insertions(+), 92 deletions(-) diff --git a/app/static/js/request/view-request-responses.js b/app/static/js/request/view-request-responses.js index e4a924289..053461823 100644 --- a/app/static/js/request/view-request-responses.js +++ b/app/static/js/request/view-request-responses.js @@ -166,7 +166,7 @@ $(function () { editor_selector: 'tinymce-area', elementpath: false, convert_urls: false, - height: 250, + height: 300, plugins: ['noneditable', 'preventdelete', 'lists'], toolbar: ['undo redo | formatselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent add_check'], setup: function (editor) { @@ -395,13 +395,13 @@ $(function () { tinymce.init({ menubar: false, // sets tinymce to enable only on specific textareas classes - mode: "specific_textareas", + mode: 'specific_textareas', // selector for tinymce textarea classes is set to 'tinymce-area' - editor_selector: "tinymce-edit-note-content", + editor_selector: 'tinymce-edit-note-content', elementpath: false, convert_urls: false, - height: 250, - plugins: ["noneditable", "preventdelete", "lists"], + height: 300, + plugins: ['noneditable', 'preventdelete', 'lists'], toolbar: ['undo redo | formatselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent add_check'], forced_root_block: '', setup: function (editor) { @@ -414,7 +414,7 @@ $(function () { editor.on('keyup', function () { let currentLength = tinyMCE.get(editor.id).getContent({format: 'text'}).trim().length; - characterCounter("#character-counter-" + editor.id, 5000, currentLength); + characterCounter('#character-counter-' + editor.id, 5000, currentLength); if (currentLength > 5000) { $('#response-' + response_id + '-next-1').prop('disabled', true); $('#' + editor.id + '-maxlength-error').show(); @@ -429,14 +429,10 @@ $(function () { // Apply parsley data required validation to note content first.find('.note-content').attr("data-parsley-required", ""); - // Apply parsley max length validation to note content - first.find('.note-content').attr("data-parsley-maxlength", "5000"); - // Apply custom validation messages first.find('.note-content').attr("data-parsley-required-message", - "Note content must be provided"); - first.find('.note-content').attr("data-parsley-maxlength-message", - "Note content must be less than 5000 characters"); + " " + + "Error, note content is required. Please type in a message."); characterCounter("#character-counter-note-" + response_id, 5000, tinyMCE.get("note-" + response_id).getContent({format: 'text'}).trim().length); @@ -534,13 +530,13 @@ $(function () { tinymce.init({ menubar: false, // sets tinymce to enable only on specific textareas classes - mode: "specific_textareas", + mode: 'specific_textareas', // selector for tinymce textarea classes is set to 'tinymce-area' - editor_selector: "tinymce-edit-instruction-content", + editor_selector: 'tinymce-edit-instruction-content', elementpath: false, convert_urls: false, - height: 250, - plugins: ["noneditable", "preventdelete", "lists"], + height: 300, + plugins: ['noneditable', 'preventdelete', 'lists'], toolbar: ['undo redo | formatselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent add_check'], forced_root_block: '', setup: function (editor) { @@ -553,7 +549,7 @@ $(function () { editor.on('keyup', function () { let currentLength = tinyMCE.get(editor.id).getContent({format: 'text'}).trim().length; - characterCounter("#character-counter-" + editor.id, 500, currentLength, 20); + characterCounter('#character-counter-' + editor.id, 500, currentLength, 20); if (currentLength > 500) { $('#response-' + response_id + '-next-1').prop('disabled', true); $('#' + editor.id + '-maxlength-error').show(); @@ -567,10 +563,16 @@ $(function () { // Apply parsley data required validation to instructions content first.find('.instruction-content').attr("data-parsley-required", ""); + first.find('.instruction-content').attr("data-parsley-minlength", 20); // Apply custom validation messages first.find('.instruction-content').attr("data-parsley-required-message", - "Instruction content must be provided"); + " " + + "Error, Offline Instructions are required. Please type in some instructions."); + + first.find('.instruction-content').attr("data-parsley-minlength-message", + " " + + "Error, Offline Instructions must be at least 20 characters."); characterCounter("#character-counter-instruction-" + response_id, 500, tinyMCE.get("instruction-" + response_id).getContent({format: 'text'}).trim().length, 20); diff --git a/app/static/styles/view_request.css b/app/static/styles/view_request.css index 5216b7c23..30610f87b 100644 --- a/app/static/styles/view_request.css +++ b/app/static/styles/view_request.css @@ -26,6 +26,8 @@ border-top: 1px solid lightgray; padding-top: 1em; padding-bottom: 1em; + max-height: 200px; + overflow-y: auto; } #request-history .event-row:not(.has-modal) { @@ -123,4 +125,8 @@ padding-bottom: 5px; min-height: 100px; margin-bottom: 10px; +} + +.tinymce-maxlength-error { + color: #df0000; } \ No newline at end of file diff --git a/app/templates/email_templates/email_response_acknowledgment.html b/app/templates/email_templates/email_response_acknowledgment.html index 685fed0dc..d41e826b9 100644 --- a/app/templates/email_templates/email_response_acknowledgment.html +++ b/app/templates/email_templates/email_response_acknowledgment.html @@ -5,15 +5,17 @@

- You can expect a response on or about {{ moment(date).format('dddd, MMMM D, YYYY') }}.

- {% if info is not none %} - Additional Information:
- {{ info }} -

- {% endif %} - Please visit {{ request_id }} to view additional information and take any necessary action. + You can expect a response on or about {{ moment(date).format('dddd, MMMM D, YYYY') }}.

+
+ {% if info is not none %} + Additional Information:
+ {{ info | safe }} +

+ {% endif %} + Please visit {{ request_id }} to view additional information and take any necessary action. +
{% else %} {{ content | safe }} {% endif %} \ No newline at end of file diff --git a/app/templates/email_templates/email_response_instruction.html b/app/templates/email_templates/email_response_instruction.html index 791daa653..b3d040938 100644 --- a/app/templates/email_templates/email_response_instruction.html +++ b/app/templates/email_templates/email_response_instruction.html @@ -8,9 +8,10 @@

This information will not be accessible to the public for 20 work days.

-
+
{{ instruction_content | safe }}
+

On {{ moment(release_date).format('dddd, MMMM D, YYYY') }} this note will be publicly available on the @@ -18,32 +19,32 @@ additional information and take any necessary action.

- {% elif privacy == response_privacy.RELEASE_AND_PRIVATE %}

The {{ agency_name }} has responded to your FOIL request {{ request_id }} with the following instructions for offline access.

-
+
{{ instruction_content | safe }}
+

Please visit {{ request_id }} to view additional information and take any necessary action.

- {% elif privacy == response_privacy.PRIVATE %}

Attention {{ agency_name }} Users,

- The following instructions for offline access were added to {{ request_id }} and is private:

+ The following instructions for offline access were added to {{ request_id }} and is private:

-
+
{{ instruction_content | safe }}
+
{% endif %} {% else %} {{ content | safe }} diff --git a/app/templates/email_templates/email_response_note.html b/app/templates/email_templates/email_response_note.html index 71ab59972..eb2473156 100644 --- a/app/templates/email_templates/email_response_note.html +++ b/app/templates/email_templates/email_response_note.html @@ -14,9 +14,10 @@

This information will not be accessible to the public for 20 work days.

-
+
{{ note_content | safe }}
+

On {{ moment(release_date).format('dddd, MMMM D, YYYY') }} this note will be publicly available on the @@ -25,31 +26,31 @@ necessary action.

- {% elif privacy == response_privacy.RELEASE_AND_PRIVATE %}

The {{ agency_name }} has responded to your FOIL request {{ request_id }} with the following note.

-
+
{{ note_content | safe }}
+

Please visit {{ request_id }} to view additional information and take any necessary action.

- {% elif privacy == response_privacy.PRIVATE %}

Attention {{ agency_name }} Users,

- The following note was added to {{ request_id }} and is private:

+ The following note was added to {{ request_id }} and is private:

-
+
{{ note_content | safe }}
+
{% endif %} {% endif %} {% else %} diff --git a/app/templates/request/responses/modal_body/determinations.html b/app/templates/request/responses/modal_body/determinations.html index f33b7e01f..cb9ad55fa 100644 --- a/app/templates/request/responses/modal_body/determinations.html +++ b/app/templates/request/responses/modal_body/determinations.html @@ -1,12 +1,12 @@ {% if response.dtype in (determination_type.ACKNOWLEDGMENT, determination_type.REOPENING) %}

Expected date of completion: {{ moment(response.date).format('dddd, MM/DD/YYYY [at] h:mm A')}}

{% if response.reason is not none %} -

{{ response.reason }}

+

{{ response.reason | safe }}

{% endif %} {% elif response.dtype == determination_type.EXTENSION %}

Due date changed to: {{ moment(response.date).format('dddd, MM/DD/YYYY [at] h:mm A') }}

{% if response.reason is not none %} -

{{ response.reason }}

+

{{ response.reason | safe }}

{% endif %} {% elif response.dtype in (determination_type.DENIAL, determination_type.CLOSING) %} {{ response.reason | format_ultimate_determination_reason | safe }} diff --git a/app/templates/request/responses/modal_body/instructions.html b/app/templates/request/responses/modal_body/instructions.html index 08547a8de..bc1c10cd5 100644 --- a/app/templates/request/responses/modal_body/instructions.html +++ b/app/templates/request/responses/modal_body/instructions.html @@ -7,21 +7,16 @@ {% if edit_response_permission %} -
diff --git a/app/templates/response/add_acknowledgment.js.html b/app/templates/response/add_acknowledgment.js.html index f3a3385dc..b0bc481e0 100644 --- a/app/templates/response/add_acknowledgment.js.html +++ b/app/templates/response/add_acknowledgment.js.html @@ -36,6 +36,41 @@ var editor = $('#acknowledgment-editor'); var confirmation = $('#acknowledgment-confirmation'); + tinymce.init({ + menubar: false, + // sets tinymce to enable only on specific textareas classes + mode: 'specific_textareas', + // selector for tinymce textarea classes is set to 'tinymce-area' + editor_selector: 'tinymce-acknowledgement-info', + elementpath: false, + convert_urls: false, + height: 300, + plugins: ['noneditable', 'preventdelete', 'lists'], + toolbar: ['undo redo | formatselect | bold italic underline | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent add_check'], + forced_root_block: '', + setup: function (editor) { + editor.ui.registry.addButton('add_check', { + text: 'Add ✔', + onAction: function () { + editor.insertContent(' ✔ '); + } + }); + + editor.on('keyup', function () { + let currentLength = tinyMCE.get('acknowledgment-email-info-text').getContent({format: 'text'}).trim().length; + characterCounter('#acknowledgment-info-character-count', 5000, currentLength); + if (currentLength > 5000) { + next2.prop('disabled', true); + $('#acknowledgement-info-maxlength-error').show(); + } + else { + next2.prop('disabled', false); + $('#acknowledgement-info-maxlength-error').hide(); + } + }); + } + }); + // Reveal / Hide Letter Generation if (generate_letters_enabled === "True") { first.show(); @@ -54,10 +89,6 @@ } email_info.attr("data-parsley-maxlength", "5000"); - email_info.keyup(function () { - characterCounter("#acknowledgment-info-character-count", 5000, $(this).val().length); - }); - // Custom Validation Messages method.attr("data-parsley-required-message", " " + @@ -98,16 +129,16 @@ email_info.attr("data-parsley-required", ""); email_info.attr("data-parsley-required-message", " " + - "Error, additional information is required when selecting a custom due date."); - email_info.attr("data-parsley-minlength", "20"); - email_info.attr("data-parsley-length-message", + "Error, additional information is required when selecting a custom due date."); + email_info.attr("data-parsley-minlength", 20); + email_info.attr("data-parsley-minlength-message", " " + - "Error, additional information must have 20 characters or more."); + "Error, additional information must have 20 characters or more."); } else { email_date.parent().hide(); email_info.removeAttr("data-parsley-required", ""); - email_info.removeAttr("data-parsley-minlength", "20"); + email_info.removeAttr("data-parsley-minlength", 20); email_info.removeClass("parsley-error"); second.find(".parsley-errors-list").children().remove(); } @@ -141,6 +172,8 @@ }); next2.click(function (e) { + tinyMCE.triggerSave(); + if (email_date.is(":visible")) { // If custom due date is selected, email_info must be validated. email_date.parsley().validate(); @@ -186,7 +219,7 @@ acknowledgment: JSON.stringify({ days: email_days.val(), date: email_date.val(), - info: email_info.val() + info: tinyMCE.get('acknowledgment-email-info-text').getContent() }), tz_name: jstz.determine().name() }, diff --git a/app/templates/response/add_extension.html b/app/templates/response/add_extension.html index 45a11354f..03ce16b64 100644 --- a/app/templates/response/add_extension.html +++ b/app/templates/response/add_extension.html @@ -51,8 +51,8 @@ value={{ request.due_date.strftime('%m/%d/%Y') }}>
-