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 |
}); |