|
Line 0
Link Here
|
| 0 |
- |
1 |
function initTinyMce(textarea){ |
|
|
2 |
let editor = tinyMCE.init({ |
| 3 |
branding : false, |
| 4 |
block_unsupported_drop : false, |
| 5 |
custom_elements:"style,link,~link", |
| 6 |
content_style: ` |
| 7 |
pre { |
| 8 |
all: unset; |
| 9 |
display: block; |
| 10 |
white-space: pre; |
| 11 |
} |
| 12 |
`, |
| 13 |
extended_valid_elements:"style,link[href|rel]", |
| 14 |
forced_root_block : '', //this is applied between BeforeSetContent and SetContent |
| 15 |
menubar : "file edit view insert format tools table", |
| 16 |
plugins: "autoresize code", |
| 17 |
//plugins : "autoresize table hr link image charmap lists code emoticons", |
| 18 |
autoresize_bottom_margin: 60, |
| 19 |
relative_urls : false, |
| 20 |
selector: "#" + textarea.id, |
| 21 |
verify_html: false, |
| 22 |
toolbar: [ |
| 23 |
"formatselect | bold italic | cut copy paste | alignleft aligncenter alignright | outdent indent | image link unlink anchor cleanup hr", |
| 24 |
"table | bullist numlist | undo redo | removeformat | emoticons charmap | forecolor backcolor | code" |
| 25 |
], |
| 26 |
entities: '', |
| 27 |
entity_encoding: 'raw', |
| 28 |
cleanup: false, |
| 29 |
remove_linebreaks: false, |
| 30 |
preserve_newlines: true, |
| 31 |
setup: function(editor){ |
| 32 |
editor.on('change', function () { |
| 33 |
editor.save(); // This updates the textarea on each change |
| 34 |
}); |
| 35 |
editor.on('BeforeSetContent', function(e){ |
| 36 |
//NOTE: this gets called by undo with "raw" format, |
| 37 |
//and we don't want to double-replace template syntax tokens |
| 38 |
if (e.format == 'html'){ |
| 39 |
//NOTE: At this point, all we have is a string, so we tokenize and put in HTML-friendly placeholders |
| 40 |
e.content = e.content.replace(/\[%([\s\S]*?)%\]/g, function(match, content) { |
| 41 |
let encoded_content = btoa(content); |
| 42 |
let placeholder = `<span class="tmpl-tag template" contenteditable="false" data-original="${encoded_content}">[%${content}%]</span>`; |
| 43 |
return placeholder; |
| 44 |
}); |
| 45 |
e.content = e.content.replace(/<<([\s\S]*?)>>/g, function(match, content) { |
| 46 |
let encoded_content = btoa(content); |
| 47 |
let rv = `<span class="tmpl-tag legacy_template" contenteditable="false" data-original="${encoded_content}"><<${content}>></span>`; |
| 48 |
return rv; |
| 49 |
}); |
| 50 |
} |
| 51 |
}); |
| 52 |
editor.on('GetContent', function(e){ |
| 53 |
//NOTE: GetContent fires after the content is serialized from the editor. |
| 54 |
//NOTE: We re-parse the content, and apply our own custom serializer, which |
| 55 |
//converts HTML placeholders back into template tokens using the appropriate syntax. |
| 56 |
//NOTE: We need a custom serializer, because otherwise our template tokens will be |
| 57 |
//escaped by the HTML serializer as if they were HTML when they're not. |
| 58 |
const parser = new DOMParser(); |
| 59 |
const doc = parser.parseFromString(e.content, "text/html"); |
| 60 |
if (doc){ |
| 61 |
const text = serialize_template(doc); |
| 62 |
if (text){ |
| 63 |
e.content = text; |
| 64 |
} |
| 65 |
} |
| 66 |
//NOTE: If we don't like the DOM-based method, there is a regex method, which works reasonably well: |
| 67 |
/* |
| 68 |
try { |
| 69 |
e.content = e.content.replace(/<span class="tmpl-tag template" contenteditable="false" data-original="([\s\S]*?)">\[%([\s\S]*?)%\]<\/span>/g,function (match,content){ |
| 70 |
let decoded_content = atob(content); |
| 71 |
let rv = `[%${decoded_content}%]`; |
| 72 |
return rv; |
| 73 |
}); |
| 74 |
} catch(error) { |
| 75 |
console.error(error); |
| 76 |
} |
| 77 |
*/ |
| 78 |
/* |
| 79 |
try { |
| 80 |
e.content = e.content.replace(/<span class="tmpl-tag legacy_template" contenteditable="false" data-original="([\s\S]*?)"><<([\s\S]*?)>><\/span>/g,function (match,content){ |
| 81 |
let decoded_content = atob(content); |
| 82 |
let rv = `<<${decoded_content}>>`; |
| 83 |
return rv; |
| 84 |
}); |
| 85 |
} catch(error){ |
| 86 |
console.error(error); |
| 87 |
} |
| 88 |
*/ |
| 89 |
}); |
| 90 |
}, |
| 91 |
}); |
| 92 |
return editor; |
| 93 |
} |
| 94 |
|
| 95 |
const void_elements = { |
| 96 |
"area": true, |
| 97 |
"base": true, |
| 98 |
"br": true, |
| 99 |
"col": true, |
| 100 |
"embed": true, |
| 101 |
"hr": true, |
| 102 |
"img": true, |
| 103 |
"input": true, |
| 104 |
"link": true, |
| 105 |
"meta": true, |
| 106 |
"param": true, |
| 107 |
"source": true, |
| 108 |
"track": true, |
| 109 |
"wbr": true |
| 110 |
}; |
| 111 |
|
| 112 |
//NOTE: This function is a custom serializer |
| 113 |
function processNode(node) { |
| 114 |
let result = ''; |
| 115 |
if (node.nodeType === Node.TEXT_NODE) { |
| 116 |
// For text nodes, simply append the raw content |
| 117 |
result += node.nodeValue; |
| 118 |
} else if (node.nodeType === Node.ELEMENT_NODE) { |
| 119 |
// For element nodes, append the tag and recursively traverse children |
| 120 |
let node_name = node.tagName.toLowerCase(); |
| 121 |
|
| 122 |
let skip = 0; |
| 123 |
if (node_name == 'body'){ |
| 124 |
skip = 1 ; |
| 125 |
} |
| 126 |
|
| 127 |
if ( node_name == 'span' && node.classList.contains('template') ){ |
| 128 |
const original_text = node.dataset.original; |
| 129 |
let decoded_content = atob(original_text); |
| 130 |
result += `[%${decoded_content}%]`; |
| 131 |
} |
| 132 |
else if ( node_name == 'span' && node.classList.contains('legacy_template') ) { |
| 133 |
const original_text = node.dataset.original; |
| 134 |
let decoded_content = atob(original_text); |
| 135 |
result += `<<${decoded_content}>>`; |
| 136 |
} |
| 137 |
else { |
| 138 |
//NOTE: This is all other regular nodes |
| 139 |
if ( !skip ) { |
| 140 |
result += `<${node.tagName.toLowerCase()}`; |
| 141 |
for ( let attr of node . attributes ) { |
| 142 |
result += ` ${attr.name}="${attr.value}"`; |
| 143 |
} |
| 144 |
result += `>`; |
| 145 |
} |
| 146 |
|
| 147 |
// Process child nodes |
| 148 |
for (let child of node.childNodes) { |
| 149 |
result += processNode(child); |
| 150 |
} |
| 151 |
if (!skip){ |
| 152 |
if (! void_elements[node_name]){ |
| 153 |
result += `</${node.tagName.toLowerCase()}>`; |
| 154 |
} |
| 155 |
} |
| 156 |
} |
| 157 |
} |
| 158 |
return result; |
| 159 |
} |
| 160 |
|
| 161 |
function serialize_template(doc) { |
| 162 |
return processNode(doc.body); |
| 163 |
} |
| 164 |
|
| 165 |
let email_inputs = document.querySelectorAll('.content_email'); |
| 166 |
email_inputs.forEach((email_input) => { |
| 167 |
let containerid = email_input.id.replace(/^content_/,""); |
| 168 |
let panel = $("#" + containerid + "_panel"); |
| 169 |
let html_btn = document.querySelector(`#is_html_${containerid}`); |
| 170 |
if (html_btn.checked){ |
| 171 |
initTinyMce(email_input); |
| 172 |
} |
| 173 |
$(`button[data-containerid=${containerid}`).on('click',function(){ |
| 174 |
let myListBox = $(panel).find('select[name="SQLfieldname"]'); |
| 175 |
if($(myListBox).find('option').length > 0) { |
| 176 |
$(myListBox).find('option').each( function (){ |
| 177 |
if ( $(this).prop('selected') && $(this).val().length > 0 ) { |
| 178 |
let editor = tinyMCE.get(email_input.id); |
| 179 |
if (editor){ |
| 180 |
editor.insertContent("<<" + $(this).val() + ">>"); |
| 181 |
} |
| 182 |
} |
| 183 |
}); |
| 184 |
} |
| 185 |
}); |
| 186 |
}); |
| 187 |
$("#noticeSampleModal").on("click", ".copy", function(){ |
| 188 |
let content = $('#noticeSampleModal .template-body').text(); |
| 189 |
let replaceid = $('#noticeSampleModal').data('replaceid'); |
| 190 |
let editor = tinyMCE.get(replaceid); |
| 191 |
if (editor){ |
| 192 |
editor.setContent(content); |
| 193 |
} |
| 194 |
}); |
| 195 |
$(".is_html_chkbox").on("click", function(el){ |
| 196 |
const target = el.target; |
| 197 |
let shortened_id = target.id.replace(/^is_html_/,""); |
| 198 |
let textarea_id = `content_${shortened_id}`; |
| 199 |
if (target.checked){ |
| 200 |
let textarea = document.querySelector(`#${textarea_id}`); |
| 201 |
initTinyMce(textarea); |
| 202 |
} else { |
| 203 |
let editor = tinyMCE.get(textarea_id); |
| 204 |
if (editor){ |
| 205 |
editor.remove(); |
| 206 |
} |
| 207 |
} |
| 208 |
}); |