Skip to content

Commit

Permalink
Add option for rtl template content (#1968)
Browse files Browse the repository at this point in the history
* feat(rtl template): refactor template content into a macro that will handle the RTL switch

* chore: regen css/js

* feat(rtl template content): add switch to template form; pass switch to backend on save; pass switch to backend on create;

* chore: formatting

* chore: add translations

* chore: update temporary translation

* chore: update temporary translatio again

* chore: 😱

* chore: formatting

* a11y(rtl): update translations, add hint for users of assistive tech

* chore: fix form label - will be overridden by .html

* feat(highlighttags): make this plugin aware of RTL attribute

* feat(templateContent.js): only run the code when the checkbox is present

* fix(rtl): include text direction in preview data

* chore(rtl): update tests

* chore: formatting

* fix(highlighttags): ensure checkbox exists before setting text direction

* feat(rtl): wrap in feature flag

* chore: update label name to match designs

* chore: formatting

* feat(rtl): remove `use_styled_checkbox` switch; implement fieldset; update design to match figma

* chore: re-gen css

* chore: add missing translation

* fix(highlightTags): only set `dir` attribute on template content

* chore: read FF_RTL from environment

* chore: bump utils

* chore: bump utils

* fix(rtl): remember rtl setting when previewing/editing

* feat(checkbox): add testid param

* test(rtl): add test-ids

* test(rtl): add ui tests

* chore: re gen poetry.lock

* chore: remove duplicated button

* chore(ci): add text-direction tests to CI suite
  • Loading branch information
andrewleith authored Oct 22, 2024
1 parent 4a17a93 commit 1d8c95d
Show file tree
Hide file tree
Showing 21 changed files with 194 additions and 47 deletions.
13 changes: 12 additions & 1 deletion app/assets/javascripts/highlightTags.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@
if (!("oninput" in document.createElement("input"))) return;

const tagPattern = /\(\(([^\)\((\?)]+)(\?\?)?([^\)\(]*)\)\)/g;
// set text_direction variable based on value of checkbox #text_direction_rtl
const textDirectionElement = document.getElementById("text_direction_rtl");
const textDirection =
textDirectionElement && textDirectionElement.checked ? "rtl" : "ltr";

Modules.HighlightTags = function () {
this.start = function (textarea) {
// only add the dir attribute if the textarea is the template_content
let extraMarkup = "";

if (textarea.attr("id") == "template_content") {
extraMarkup = ` dir="${textDirection}" `;
}

this.$textbox = $(textarea)
.wrap(
`
<div class='textbox-highlight-wrapper' />
<div class='textbox-highlight-wrapper'${extraMarkup}/>
`,
)
.after(
Expand Down
20 changes: 20 additions & 0 deletions app/assets/javascripts/templateContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* This module enhances the template content text area with a switch to enable right-to-left text direction.
*/
(function () {
const checkbox = document.getElementById("text_direction_rtl");
const content = document.getElementById("template_content");

// update the dir attribute when checkbox is clicked
if (checkbox) {
checkbox.addEventListener("change", function () {
content.dir = this.checked ? "rtl" : "ltr";
content.closest(".textbox-highlight-wrapper").dir = content.dir;
});

// on page load, if the checkbox is checked, set the dir attribute
if (checkbox.checked) {
content.dir = "rtl";
}
}
})();
2 changes: 1 addition & 1 deletion app/assets/stylesheets/index.css

Large diffs are not rendered by default.

15 changes: 3 additions & 12 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class Config(object):

# FEATURE FLAGS
FF_SALESFORCE_CONTACT = env.bool("FF_SALESFORCE_CONTACT", True)
FF_RTL = env.bool("FF_RTL", True)

FREE_YEARLY_EMAIL_LIMIT = env.int("FREE_YEARLY_EMAIL_LIMIT", 10_000_000)
FREE_YEARLY_SMS_LIMIT = env.int("FREE_YEARLY_SMS_LIMIT", 25_000)
Expand Down Expand Up @@ -211,12 +212,7 @@ class Test(Development):
SYSTEM_STATUS_URL = "https://localhost:3000"
NO_BRANDING_ID = "0af93cf1-2c49-485f-878f-f3e662e651ef"

FF_SPIKE_SMS_DAILY_LIMIT = True
FF_SMS_PARTS_UI = False
FF_BOUNCE_RATE_V1 = True
FF_BOUNCE_RATE_V15 = True
FF_BOUNCE_RATE_BACKEND = True
FF_ABTEST_SERVICE_ID = ""
FF_RTL = True


class ProductionFF(Config):
Expand All @@ -242,12 +238,7 @@ class ProductionFF(Config):
SYSTEM_STATUS_URL = "https://localhost:3000"
NO_BRANDING_ID = "0af93cf1-2c49-485f-878f-f3e662e651ef"

FF_SPIKE_SMS_DAILY_LIMIT = True
FF_SMS_PARTS_UI = False
FF_BOUNCE_RATE_V1 = True
FF_BOUNCE_RATE_V15 = True
FF_BOUNCE_RATE_BACKEND = True
FF_ABTEST_SERVICE_ID = ""
FF_RTL = False


class Production(Config):
Expand Down
5 changes: 4 additions & 1 deletion app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,9 @@ class BaseTemplateForm(StripWhitespaceForm):
NoCommasInPlaceHolders(),
],
)

text_direction_rtl = BooleanField("text_direction_rtl")

process_type = RadioField(
_l("Select a priority queue"),
choices=[
Expand Down Expand Up @@ -838,7 +841,7 @@ class EmailTemplateFormWithCategory(BaseTemplateFormWithCategory):
subject = TextAreaField(_l("Subject line of the email"), validators=[DataRequired(message=_l("This cannot be empty"))])

template_content = TextAreaField(
_l("Email message"),
_l("Email content"),
validators=[
DataRequired(message=_l("This cannot be empty")),
NoCommasInPlaceHolders(),
Expand Down
6 changes: 6 additions & 0 deletions app/main/views/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ def preview_template(service_id, template_id=None):
template["subject"],
None if template["process_type"] == TC_PRIORITY_VALUE else template["process_type"],
template["template_category_id"],
template["text_direction_rtl"],
)
else:
new_template = service_api_client.create_service_template(
Expand Down Expand Up @@ -767,6 +768,7 @@ def add_service_template(service_id, template_type, template_folder_id=None): #
"process_type": form.process_type.data,
"folder": template_folder_id,
"template_category_id": form.template_category_id.data,
"text_direction_rtl": form.text_direction_rtl.data,
}
set_preview_data(preview_template_data, service_id)
return redirect(
Expand Down Expand Up @@ -897,6 +899,8 @@ def edit_service_template(service_id, template_id):
template["content"] = new_template_data["content"]
template["name"] = new_template_data["name"]
template["subject"] = new_template_data["subject"]
template["text_direction_rtl"] = new_template_data["text_direction_rtl"]

template["template_content"] = template["content"]

if template.get("process_type_column") is None:
Expand All @@ -920,6 +924,7 @@ def edit_service_template(service_id, template_id):
"reply_to_text": template["reply_to_text"],
"folder": template["folder"],
"template_category_id": form.template_category_id.data,
"text_direction_rtl": form.text_direction_rtl.data,
}
set_preview_data(new_template_data, service_id, template_id)

Expand Down Expand Up @@ -959,6 +964,7 @@ def edit_service_template(service_id, template_id):
subject,
None if form.process_type.data == TC_PRIORITY_VALUE else form.process_type.data,
form.template_category_id.data,
form.text_direction_rtl.data,
)
# Send the information in form's template_category_other field to Freshdesk
# This code path is a little complex - We do not want to raise an error if the request to Freshdesk fails, only if template creation fails
Expand Down
23 changes: 22 additions & 1 deletion app/notify_client/service_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def create_service_template(
process_type="normal",
parent_folder_id=None,
template_category_id=None,
text_direction_rtl=False,
):
"""
Create a service template.
Expand All @@ -208,6 +209,11 @@ def create_service_template(
"process_type": process_type,
"template_category_id": template_category_id,
}

# Move this into `data` dictionary above 👆 when FF_RTL removed
if current_app.config["FF_RTL"]:
data["text_direction_rtl"] = text_direction_rtl

if subject:
data.update({"subject": subject})
if parent_folder_id:
Expand All @@ -220,7 +226,16 @@ def create_service_template(
@cache.delete("template-{id_}-version-None")
@cache.delete("template-{id_}-versions")
def update_service_template(
self, id_, name, type_, content, service_id, subject=None, process_type=None, template_category_id=None
self,
id_,
name,
type_,
content,
service_id,
subject=None,
process_type=None,
template_category_id=None,
text_direction_rtl=False,
):
"""
Update a service template.
Expand All @@ -233,7 +248,13 @@ def update_service_template(
"service": service_id,
"template_category_id": template_category_id,
"process_type": process_type,
"text_direction_rtl": text_direction_rtl,
}

# Move this into `data` dictionary above 👆 when FF_RTL removed
if current_app.config["FF_RTL"]:
data["text_direction_rtl"] = text_direction_rtl

if subject:
data.update({"subject": subject})

Expand Down
7 changes: 5 additions & 2 deletions app/templates/components/checkbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
{% endmacro %}


{% macro checkbox_input(id, name, data=None, value="y") %}
{% macro checkbox_input(id, name, data=None, value="y", class="", testid=None) %}
<input
id="{{ id }}" name="{{ name }}" type="checkbox" value="{{ value }}"
id="{{ id }}" name="{{ name }}" type="checkbox" value="{{ value }}" class="{{ class }}"
{% if data %}
checked
{% endif %}
{% if testid %}
data-testid="{{ testid }}"
{% endif %}
>
{% endmacro %}

Expand Down
21 changes: 21 additions & 0 deletions app/templates/components/template-content.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% from "components/checkbox.html" import checkbox, checkbox_input %}
{% from "components/textbox.html" import textbox %}

{% macro template_content(content_field, checkbox_field) %}
<!-- TODO: remove `use_styled_checkbox` and this if statement once we decide on the UI element to use here -->

<fieldset class="contain-floats w-full" role="radiogroup">
<legend>
<span>{{ _('Email message') }}</span>
</legend>
{% if config["FF_RTL"] %}
<div class="p-2 bg-gray-300 flex items-center">
{{ checkbox_input(checkbox_field.id, checkbox_field.name, checkbox_field.data, class="h-8 w-8 mr-2", testid="template-rtl") }}
<label class="pb-0 inline-block text-small" for="text_direction_rtl">{{ _('Display') }}<span class="sr-only"> {{ _('email content') }} </span> {{ _('right to left') }}</label>
</div>
{% endif %}

{{ textbox(content_field, highlight_tags=True, width='w-full', rows=8, testid="template-content", label_class="sr-only" ) }}
</fieldset>

{% endmacro %}
7 changes: 4 additions & 3 deletions app/templates/components/textbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
rows=8,
required=true,
is_page_heading=false,
testid=None
testid=None,
label_class=None
) %}
<div
class="form-group contain-floats box-border mb-gutterHalf md:mb-gutter{% if field.errors %} form-group-error{% endif %}"
Expand All @@ -22,9 +23,9 @@
{% set describedby = '' %}

{% if is_page_heading %}
<h1><label class="form-label heading-large" for="{{ field.name }}" >
<h1><label class="form-label heading-large{% if label_class %} {{ label_class }} {% endif %}" for="{{ field.name }}" >
{% else %}
<label class="form-label" for="{{ field.name }}" >
<label class="form-label{% if label_class %} {{ label_class }} {% endif %}" for="{{ field.name }}" >
{% endif %}
{% if label %}
{{ label }}
Expand Down
7 changes: 4 additions & 3 deletions app/templates/views/edit-email-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{% from "components/form.html" import form_wrapper %}
{% from "components/task-shortcut.html" import task_shortcut %}
{% from "components/template-category.html" import template_category %}
{% from "components/template-content.html" import template_content with context%}

{% block service_page_title %}
{{ heading }}
Expand Down Expand Up @@ -50,9 +51,9 @@
{{ textbox(form.name, width='w-full', hint=_('This will not show in the message. Use a name that helps you find the template when you need it.'), rows=10, testid="template-name") }}

{{ textbox(form.subject, width='w-full', highlight_tags=True, rows=2, hint=_("Tell recipients what the email is about. Try to use less than 10 words.", testid="template-subject")) }}

{{ textbox(form.template_content, highlight_tags=True, width='w-full', rows=8, testid="template-content" ) }}

{{ template_content(form.template_content, form.text_direction_rtl) }}
<h2 class="heading-medium">{{ _('Template category') }}</h2>
{% call template_category(form.template_category_id, true if template_category_mode == 'expand' else false) %}
{{ select(form.template_category_id, hint=_('Template categories help improve delivery of your messages'), option_hints=template_category_hints, option_conditionals=other_category, testid="template-categories", use_aria_labelledby=false) }}
Expand Down
2 changes: 1 addition & 1 deletion app/templates/views/templates/_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<h2 class="heading-medium">{{ _('Ready to send?') }}</h2>

<div class="mb-12">
<a href="{{ url_for('.add_recipients', service_id=current_service.id, template_id=template.id) }}" class="button">
<a href="{{ url_for('.add_recipients', service_id=current_service.id, template_id=template.id) }}" class="button" data-testid="add-recipients">
{{ _('Yes, add recipients') }}
</a>
<a href="{{ url_for('.send_test', service_id=current_service.id, template_id=template.id) }}" class="button button-secondary">{{ _('No, send yourself this message') }}</a>
Expand Down
6 changes: 5 additions & 1 deletion app/translations/csv/fr.csv
Original file line number Diff line number Diff line change
Expand Up @@ -1986,4 +1986,8 @@
"The service {} responded in {} seconds.","Le service {} a répondu en {} secondes."
"Are you sure you want to delete this callback configuration?","Voulez-vous vraiment supprimer cette configuration de rappel?"
"Your Callback configuration has been deleted.","Votre configuration de rappel a été supprimée."
"The service {} took longer than 1 second to respond.","Le service {} a mis plus d’une seconde à répondre."
"The service {} took longer than 1 second to respond.","Le service {} a mis plus d’une seconde à répondre."
"Display","Afficher"
"email content","le message du courriel"
"Email content","Message du courriel"
"right to left","de droite à gauche"
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const javascripts = () => {
paths.src + "javascripts/notificationsReports.js",
paths.src + "javascripts/main.js",
paths.src + "javascripts/templateCategories.js",
paths.src + "javascripts/templateContent.js",
])
.pipe(plugins.prettyerror())
.pipe(
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ unidecode = "^1.3.8"

# PaaS
awscli-cwlogs = "^1.4.6"
notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", tag = "52.3.3" }
notifications-utils = { git = "https://github.com/cds-snc/notifier-utils.git", tag = "52.3.5" }


# Pinned dependencies
certifi = "^2024.0.0" # Pinned for security reasons: https://github.com/cds-snc/notification-admin/security/dependabot/169
Expand Down
Loading

0 comments on commit 1d8c95d

Please sign in to comment.