Skip to content

Commit

Permalink
Merge pull request #33897 from dimagi/proteusvacuum-fr/add-rich-text-…
Browse files Browse the repository at this point in the history
…emails-waf

Proteusvacuum fr/add rich text emails waf
  • Loading branch information
esoergel committed Dec 19, 2023
2 parents 3c881c1 + 28c2890 commit 09fec58
Show file tree
Hide file tree
Showing 30 changed files with 2,004 additions and 58 deletions.
188 changes: 188 additions & 0 deletions corehq/apps/hqwebapp/static/hqwebapp/js/ckeditor_knockout_bindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/* global CKEditor5 */

// When adding a ckeditor binding, be sure to also add the name of an image upload url.
// For example <textarea data-bind="ckeditor: message" data-image-upload-url="upload_messaging_image"></textarea>

hqDefine('hqwebapp/js/ckeditor_knockout_bindings', [
'jquery',
'underscore',
'knockout',
'hqwebapp/js/initial_page_data',
], function (
$,
_,
ko,
initialPageData
) {
ko.bindingHandlers.ckeditor = {
init: function (element, valueAccessor) {
var options = {
plugins: [
CKEditor5.alignment.Alignment,
CKEditor5.link.AutoLink,
CKEditor5.autoformat.Autoformat,
CKEditor5.basicStyles.Bold,
CKEditor5.basicStyles.Italic,
CKEditor5.essentials.Essentials,
CKEditor5.font.Font,
CKEditor5.font.FontColor,
CKEditor5.heading.Heading,
CKEditor5.horizontalLine.HorizontalLine,
CKEditor5.htmlSupport.GeneralHtmlSupport,
CKEditor5.image.Image,
CKEditor5.image.ImageCaption,
CKEditor5.image.ImageStyle,
CKEditor5.image.ImageResize,
CKEditor5.image.ImageResizeButtons,
CKEditor5.image.ImageToolbar,
CKEditor5.image.ImageUpload,
CKEditor5.indent.Indent,
CKEditor5.link.Link,
CKEditor5.link.LinkImage,
CKEditor5.list.List,
CKEditor5.paragraph.Paragraph,
CKEditor5.pasteFromOffice.PasteFromOffice,
CKEditor5.restrictedEditing.RestrictedEditingMode,
CKEditor5.upload.SimpleUploadAdapter,
],
toolbar: {
items: [
'heading',
'fontFamily',
'fontSize',
'fontColor',
'|',
'bold',
'italic',
'link',
'alignment',
'bulletedList',
'numberedList',
'uploadImage',
'|',
'outdent',
'indent',
'|',
'undo',
'redo',
'restrictedEditing',
],
},
image: {
insert: {
type: 'inline',
},
toolbar: [
'imageStyle:side',
'|',
'toggleImageCaption',
'|',
'linkImage',
],
},
simpleUpload: {
uploadUrl: initialPageData.reverse(element.attributes['data-image-upload-url'].value),
withCredentials: true,
headers: {
'X-CSRFTOKEN': $("#csrfTokenContainer").val(),
},
},
htmlSupport: {
// We allow all HTML here, and filter it out in a sanitizing step
allow: [
{
name: /.*/,
attributes: true,
classes: true,
styles: true,
},
],
},
restrictedEditing: {
allowedCommands: [
"alignment",
"fontColor",
"fontBackgroundColor",
"deleteForward",
"forwardDelete",
"delete",
"bold",
"italic",
"enter",
"selectAll",
"shiftEnter",
"insertText",
"input",
"undo",
"redo",
"fontFamily",
"fontSize",
"paragraph",
"insertParagraph",
"heading",
"horizontalLine",
"insertImage",
"replaceImageSource",
"imageInsert",
"imageTextAlternative",
"imageTypeInline",
"toggleImageCaption",
"imageStyle",
"resizeImage",
"imageResize",
"uploadImage",
"imageUpload",
"indent",
"outdent",
"link",
"unlink",
"numberedList",
"bulletedList",
"indentList",
"outdentList",
],
},
},
editorInstance = undefined;

CKEditor5.editorClassic.ClassicEditor.create(element, options).then(function (editor) {
var isSubscriberChange = false,
isEditorChange = false,
editorInstance = editor;
if (typeof ko.utils.unwrapObservable(valueAccessor()) !== "undefined") {
editorInstance.setData(ko.utils.unwrapObservable(valueAccessor()));
}

// Update the observable value when the document changes
editorInstance.model.document.on('change:data', function () {
if (!isSubscriberChange) {
isEditorChange = true;
valueAccessor()(editorInstance.getData());
isEditorChange = false;
}

});

// Update the document whenever the observable changes
valueAccessor().subscribe(function (value) {
if (!isEditorChange) {
isSubscriberChange = true;
editorInstance.setData(value);
isSubscriberChange = false;
}

});

if (initialPageData.get('read_only_mode')) {
editorInstance.enableReadOnlyMode('');
}
});

// handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
CKEditor5.editorClassic.ClassicEditor.remove(editorInstance);
});

},
};
});
18 changes: 18 additions & 0 deletions corehq/apps/sms/migrations/0058_email_html_body.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.20 on 2023-11-06 14:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('sms', '0057_fcm_content_type_messaging_events'),
]

operations = [
migrations.AddField(
model_name='email',
name='html_body',
field=models.TextField(null=True),
),
]
1 change: 1 addition & 0 deletions corehq/apps/sms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2678,3 +2678,4 @@ class Email(models.Model):
recipient_address = models.CharField(max_length=255, db_index=True)
subject = models.TextField(null=True)
body = models.TextField(null=True)
html_body = models.TextField(null=True)
1 change: 1 addition & 0 deletions corehq/blobs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class CODES:
demo_user_restore = 14 # DemoUserRestore
data_file = 15 # domain data file (see DataFile class)
form_multimedia = 16 # form submission multimedia zip
email_multimedia = 17 # email images and attachments


CODES.name_of = {code: name
Expand Down
111 changes: 111 additions & 0 deletions corehq/messaging/scheduling/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,114 @@
VISIT_WINDOW_START = 'WINDOW_START'
VISIT_WINDOW_END = 'WINDOW_END'
VISIT_WINDOW_DUE_DATE = 'WINDOW_DUE_DATE'


ALLOWED_HTML_TAGS = {
"h2",
"h3",
"h4",
"h5",
"h6",
"ul",
"ol",
"li",
"p",
"br",
"a",
"strong",
"i",
"em",
"u",
"b",
"s",
"span",
"div",
"sub",
"sup",
"blockquote",
"code",
"img",
"figure",
"figcaption",
"table",
"tbody",
"tr",
"td",
"html",
"head",
"meta",
"title",
"body",
"style",
}


ALLOWED_HTML_ATTRIBUTES = {
'a': ['href', 'title'],
'abbr': ['title'],
'acronym': ['title'],
'div': ['style', 'class'],
'span': ['style', 'class'],
'img': ['style', 'src', 'width', 'height', 'class'],
'figcaption': ['style', 'class'],
'figure': ['style', 'class'],
'table': ['class', 'role','cellspacing', 'cellpadding', 'border', 'align', 'width'],
'td': ['valign'],
'meta': ['charset', 'name', 'viewport', 'content', 'initial-scale']
}

ALLOWED_CSS_PROPERTIES = {
"aspect-ratio",
"azimuth",
"background-color",
"border-bottom-color",
"border-collapse",
"border-color",
"border-left-color",
"border-right-color",
"border-top-color",
"clear",
"color",
"cursor",
"direction",
"display",
"elevation",
"float",
"font",
"font-family",
"font-size",
"font-style",
"font-variant",
"font-weight",
"height",
"letter-spacing",
"line-height",
"margin",
"overflow",
"padding",
"pause",
"pause-after",
"pause-before",
"pitch",
"pitch-range",
"richness",
"speak",
"speak-header",
"speak-numeral",
"speak-punctuation",
"speech-rate",
"stress",
"text-align",
"text-decoration",
"text-indent",
"unicode-bidi",
"vertical-align",
"voice-family",
"volume",
"white-space",
"width",
}


MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 # 1MB
VALID_EMAIL_IMAGE_MIMETYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"}

0 comments on commit 09fec58

Please sign in to comment.