View | Details | Raw Unified | Return to bug 11583
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/letter.tt (-4 / +9 lines)
Lines 648-662 Link Here
648
                                <label for="is_html_[% letter.message_transport_type | html %]_[% lang | html %]">HTML message:</label>
648
                                <label for="is_html_[% letter.message_transport_type | html %]_[% lang | html %]">HTML message:</label>
649
                                [% IF letter.is_html %]
649
                                [% IF letter.is_html %]
650
                                    [% IF sms_readonly %]
650
                                    [% IF sms_readonly %]
651
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" checked="checked" disabled="disabled" />
651
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" class="is_html_chkbox" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" checked="checked" disabled="disabled" />
652
                                    [% ELSE %]
652
                                    [% ELSE %]
653
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" checked="checked" />
653
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" class="is_html_chkbox" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" checked="checked" />
654
                                    [% END %]
654
                                    [% END %]
655
                                [% ELSE %]
655
                                [% ELSE %]
656
                                    [% IF sms_readonly %]
656
                                    [% IF sms_readonly %]
657
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" disabled="disabled" />
657
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" class="is_html_chkbox" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" disabled="disabled" />
658
                                    [% ELSE %]
658
                                    [% ELSE %]
659
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" />
659
                                        <input type="checkbox" name="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" class="is_html_chkbox" id="is_html_[% letter.message_transport_type | html %]_[% lang | html %]" value="1" />
660
                                    [% END %]
660
                                    [% END %]
661
                                [% END %]
661
                                [% END %]
662
                            </li>
662
                            </li>
Lines 835-840 Link Here
835
        });
835
        });
836
     </script>
836
     </script>
837
    [% Asset.js("js/letter.js") | $raw %]
837
    [% Asset.js("js/letter.js") | $raw %]
838
    [% IF Koha.Preference('UseWYSIWYGinNotices') %]
839
        [% Asset.js("lib/tiny_mce/tinymce.min.js") | $raw %]
840
        [% INCLUDE 'str/tinymce_i18n.inc' %]
841
        [% Asset.js("js/letter_editor.js") | $raw %]
842
    [% END %]
838
[% END %]
843
[% END %]
839
844
840
[% INCLUDE 'intranet-bottom.inc' %]
845
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/letter_editor.js (-1 / +208 lines)
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}">&lt;&lt;${content}&gt;&gt;</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]*?)">&lt;&lt;([\s\S]*?)&gt;&gt;<\/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
    });

Return to bug 11583