PageSourceSearch

https://www.akrontecnologia.com.br/_cdn/richtext/jquery.richtext.js?v=1763755633

js akrontecnologia.com.br collected 2026-10-02 01:18:15 UTC 93,410 bytes, 2,162 lines download raw bytes

1/*
2 RichText: WYSIWYG editor developed as jQuery plugin
3
4 @name RichText
5 @author https://github.com/webfashionist - Bob Schockweiler - [email protected]
6 @license GNU AFFERO GENERAL PUBLIC LICENSE Version 3
7 @preserve
8
9 Copyright (C) 2020 Bob Schockweiler ( [email protected] )
10
11 This program is free software: you can redistribute it and/or modify
12 it under the terms of the GNU Affero General Public License as published
13 by the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 GNU Affero General Public License for more details.
20
21 You should have received a copy of the GNU Affero General Public License
22 along with this program.  If not, see <http://www.gnu.org/licenses/>.
23*/
24
25(function ($) {
26
27    $.fn.richText = function (options) {
28
29        // set default options
30        // and merge them with the parameter options
31        var settings = $.extend({
32
33            // text formatting
34            bold: true,
35            italic: true,
36            underline: true,
37
38            // text alignment
39            leftAlign: false,
40            centerAlign: false,
41            rightAlign: false,
42            justify: false,
43
44            // lists
45            ol: true,
46            ul: true,
47
48            // title
49            heading: false,
50
51            // fonts
52            fonts: false,
53            fontList: ["Arial",
54                "Arial Black",
55                "Comic Sans MS",
56                "Courier New",
57                "Geneva",
58                "Georgia",
59                "Helvetica",
60                "Impact",
61                "Lucida Console",
62                "Tahoma",
63                "Times New Roman",
64                "Verdana"
65            ],
66            fontColor: false,
67            fontSize: false,
68
69            // uploads
70            imageUpload: false,
71            fileUpload: false,
72
73            // media
74            videoEmbed: false,
75
76            // link
77            urls: false,
78
79            // tables
80            table: false,
81
82            // code
83            removeStyles: false,
84            code: false,
85
86            // colors
87            colors: [],
88
89            // dropdowns
90            fileHTML: '',
91            imageHTML: '',
92
93            // translations
94            translations: {
95                'title': 'Título',
96                'white': 'Branco',
97                'black': 'Preto',
98                'brown': 'Marrom',
99                'beige': 'Bege',
100                'darkBlue': 'Azul Escuro',
101                'blue': 'Azul',
102                'lightBlue': 'Azul Claro',
103                'darkRed': 'Vermelho Escuro',
104                'red': 'Vermelho',
105                'darkGreen': 'Verde Escuro',
106                'green': 'Verde',
107                'purple': 'Roxo',
108                'darkTurquois': 'Turquesa Escuro',
109                'turquois': 'Turquesa',
110                'darkOrange': 'Laranja Escuro',
111                'orange': 'Laranja',
112                'yellow': 'Amarelo',
113                'imageURL': 'URL da Imagem',
114                'fileURL': 'URL do Arquivo',
115                'linkText': 'Texto do Link',
116                'url': 'URL',
117                'size': 'Tamanho',
118                'responsive': 'Responsivo',
119                'text': 'Texto',
120                'openIn': 'Abrir em',
121                'sameTab': 'Mesma Guia',
122                'newTab': 'Nova Guia',
123                'align': 'Alinhamento',
124                'left': 'Esquerdo',
125                'justify': 'Justificar',
126                'center': 'Centro',
127                'right': 'Direito',
128                'rows': 'Linhas',
129                'columns': 'Colunas',
130                'add': 'Adicionar',
131                'pleaseEnterURL': 'Insira uma URL',
132                'videoURLnotSupported': 'URL de vídeo não compatível',
133                'pleaseSelectImage': 'Selecione uma imagem',
134                'pleaseSelectFile': 'Selecione um arquivo',
135                'bold': 'Negrito',
136                'italic': 'Itálico',
137                'underline': 'Sublinhado',
138                'alignLeft': 'Alinhar à Esquerda',
139                'alignCenter': 'Centralizar',
140                'alignRight': 'Alinhar à Direita',
141                'addOrderedList': 'Numeração',
142                'addUnorderedList': 'Marcadores',
143                'addHeading': 'Estilo de Título',
144                'addFont': 'Fontes',
145                'addFontColor': 'Cor da Fonte',
146                'addFontSize': 'Tamanho da Fonte',
147                'addImage': 'Adicionar Imagem',
148                'addVideo': 'Adicionar Vídeo',
149                'addFile': 'Adicionar Arquivo',
150                'addURL': 'Inserir Link',
151                'addTable': 'Adicionar Tabela',
152                'removeStyles': 'Remover Estilos',
153                'code': 'Exibir Código HTML',
154                'undo': 'Desfazer',
155                'redo': 'Refazer',
156                'close': 'Fechar'
157            },
158
159            // privacy
160            youtubeCookies: false,
161
162            // dev settings
163            useSingleQuotes: false,
164            height: 0,
165            heightPercentage: 0,
166            id: "",
167            class: "",
168            useParagraph: false,
169            maxlength: 0,
170            callback: undefined
171
172        }, options);
173
174
175        /* prepare toolbar */
176        var $inputElement = $(this);
177        $inputElement.addClass("richText-initial");
178        var $editor,
179            $toolbarList = $('<ul />'),
180            $toolbarElement = $('<li />'),
181            $btnBold = $('<a />', {
182                class: "richText-btn",
183                "data-command": "bold",
184                "title": settings.translations.bold,
185                html: '<span class="fa fa-bold"></span>'
186            }), // bold
187            $btnItalic = $('<a />', {
188                class: "richText-btn",
189                "data-command": "italic",
190                "title": settings.translations.italic,
191                html: '<span class="fa fa-italic"></span>'
192            }), // italic
193            $btnUnderline = $('<a />', {
194                class: "richText-btn",
195                "data-command": "underline",
196                "title": settings.translations.underline,
197                html: '<span class="fa fa-underline"></span>'
198            }), // underline
199            $btnJustify = $('<a />', {
200                class: "richText-btn",
201                "data-command": "justifyFull",
202                "title": settings.translations.justify,
203                html: '<span class="fa fa-align-justify"></span>'
204            }), // left align
205            $btnLeftAlign = $('<a />', {
206                class: "richText-btn",
207                "data-command": "justifyLeft",
208                "title": settings.translations.alignLeft,
209                html: '<span class="fa fa-align-left"></span>'
210            }), // left align
211            $btnCenterAlign = $('<a />', {
212                class: "richText-btn",
213                "data-command": "justifyCenter",
214                "title": settings.translations.alignCenter,
215                html: '<span class="fa fa-align-center"></span>'
216            }), // centered
217            $btnRightAlign = $('<a />', {
218                class: "richText-btn",
219                "data-command": "justifyRight",
220                "title": settings.translations.alignRight,
221                html: '<span class="fa fa-align-right"></span>'
222            }), // right align
223            $btnOL = $('<a />', {
224                class: "richText-btn",
225                "data-command": "insertOrderedList",
226                "title": settings.translations.addOrderedList,
227                html: '<span class="fa fa-list-ol"></span>'
228            }), // ordered list
229            $btnUL = $('<a />', {
230                class: "richText-btn",
231                "data-command": "insertUnorderedList",
232                "title": settings.translations.addUnorderedList,
233                html: '<span class="fa fa-list"></span>'
234            }), // unordered list
235            $btnHeading = $('<a />', {
236                class: "richText-btn",
237                "title": settings.translations.addHeading,
238                html: '<span class="fa fa-header fa-heading"></span>'
239            }), // title/header
240            $btnFont = $('<a />', {
241                class: "richText-btn",
242                "title": settings.translations.addFont,
243                html: '<span class="fa fa-font"></span>'
244            }), // font color
245            $btnFontColor = $('<a />', {
246                class: "richText-btn",
247                "title": settings.translations.addFontColor,
248                html: '<span class="fa fa-paint-brush"></span>'
249            }), // font color
250            $btnFontSize = $('<a />', {
251                class: "richText-btn",
252                "title": settings.translations.addFontSize,
253                html: '<span class="fa fa-text-height"></span>'
254            }), // font color
255            $btnImageUpload = $('<a />', {
256                class: "richText-btn",
257                "title": settings.translations.addImage,
258                html: '<span class="fa fa-image"></span>'
259            }), // image
260            $btnVideoEmbed = $('<a />', {
261                class: "richText-btn",
262                "title": settings.translations.addVideo,
263                html: '<span class="fa fa-video-camera fa-video"></span>'
264            }), // video
265            $btnFileUpload = $('<a />', {
266                class: "richText-btn",
267                "title": settings.translations.addFile,
268                html: '<span class="fa fa-file-text-o far fa-file-alt"></span>'
269            }), // file
270            $btnURLs = $('<a />', {
271                class: "richText-btn",
272                "title": settings.translations.addURL,
273                html: '<span class="fa fa-link"></span>'
274            }), // urls/links
275            $btnTable = $('<a />', {
276                class: "richText-btn",
277                "title": settings.translations.addTable,
278                html: '<span class="fa fa-table"></span>'
279            }), // table
280            $btnRemoveStyles = $('<a />', {
281                class: "richText-btn",
282                "data-command": "removeFormat",
283                "title": settings.translations.removeStyles,
284                html: '<span class="fa fa-eraser"></span>'
285            }), // clean up styles
286            $btnCode = $('<a />', {
287                class: "richText-btn",
288                "data-command": "toggleCode",
289                "title": settings.translations.code,
290                html: '<span class="fa fa-code"></span>'
291            }); // code
292
293
294        /* prepare toolbar dropdowns */
295        var $dropdownOuter = $('<div />', {class: "richText-dropdown-outer"});
296        var $dropdownClose = $('<span />', {
297            class: "richText-dropdown-close",
298            html: '<span title="' + settings.translations.close + '"><span class="fa fa-times"></span></span>'
299        });
300        var $dropdownList = $('<ul />', {class: "richText-dropdown"}), // dropdown lists
301            $dropdownBox = $('<div />', {class: "richText-dropdown"}), // dropdown boxes / custom dropdowns
302            $form = $('<div />', {class: "richText-form"}), // symbolic form
303            $formItem = $('<div />', {class: 'richText-form-item'}), // form item
304            $formLabel = $('<label />'), // form label
305            $formInput = $('<input />', {type: "text"}), //form input field
306            $formInputFile = $('<input />', {type: "file"}), // form file input field
307            $formInputSelect = $('<select />'),
308            $formButton = $('<button />', {text: settings.translations.add, class: "btn"}); // button
309
310        /* internal settings */
311        var savedSelection; // caret position/selection
312        var editorID = "richText-" + Math.random().toString(36).substring(7);
313        var ignoreSave = false, $resizeImage = null;
314
315        /* prepare editor history */
316        var history = [];
317        history[editorID] = [];
318        var historyPosition = [];
319        historyPosition[editorID] = 0;
320
321        /* list dropdown for titles */
322        var $titles = $dropdownList.clone();
323        $titles.append($('<li />', {html: '<a data-command="formatBlock" data-option="h1">' + settings.translations.title + ' #1</a>'}));
324        $titles.append($('<li />', {html: '<a data-command="formatBlock" data-option="h2">' + settings.translations.title + ' #2</a>'}));
325        $titles.append($('<li />', {html: '<a data-command="formatBlock" data-option="h3">' + settings.translations.title + ' #3</a>'}));
326        $titles.append($('<li />', {html: '<a data-command="formatBlock" data-option="h4">' + settings.translations.title + ' #4</a>'}));
327        $btnHeading.append($dropdownOuter.clone().append($titles.prepend($dropdownClose.clone())));
328
329        /* list dropdown for fonts */
330        var fonts = settings.fontList;
331        var $fonts = $dropdownList.clone();
332        for (var i = 0; i < fonts.length; i++) {
333            $fonts.append($('<li />', {html: '<a style="font-family:' + fonts[i] + ';" data-command="fontName" data-option="' + fonts[i] + '">' + fonts[i] + '</a>'}));
334        }
335        $btnFont.append($dropdownOuter.clone().append($fonts.prepend($dropdownClose.clone())));
336
337        /* list dropdown for font sizes */
338        var fontSizes = [24, 18, 16, 14, 12];
339        var $fontSizes = $dropdownList.clone();
340        for (var i = 0; i < fontSizes.length; i++) {
341            $fontSizes.append($('<li />', {html: '<a style="font-size:' + fontSizes[i] + 'px;" data-command="fontSize" data-option="' + fontSizes[i] + '">' + settings.translations.text + ' ' + fontSizes[i] + 'px</a>'}));
342        }
343        $btnFontSize.append($dropdownOuter.clone().append($fontSizes.prepend($dropdownClose.clone())));
344
345        /* font colors */
346        var $fontColors = $dropdownList.clone();
347        $fontColors.html(loadColors("forecolor"));
348        $btnFontColor.append($dropdownOuter.clone().append($fontColors.prepend($dropdownClose.clone())));
349
350
351        /* background colors */
352        //var $bgColors = $dropdownList.clone();
353        //$bgColors.html(loadColors("hiliteColor"));
354        //$btnBGColor.append($dropdownOuter.clone().append($bgColors));
355
356        /* box dropdown for links */
357        var $linksDropdown = $dropdownBox.clone();
358        var $linksForm = $form.clone().attr("id", "richText-URL").attr("data-editor", editorID);
359        $linksForm.append(
360            $formItem.clone()
361                .append($formLabel.clone().text(settings.translations.url).attr("for", "url"))
362                .append($formInput.clone().attr("id", "url"))
363        );
364        $linksForm.append(
365            $formItem.clone()
366                .append($formLabel.clone().text(settings.translations.text).attr("for", "urlText"))
367                .append($formInput.clone().attr("id", "urlText"))
368        );
369        $linksForm.append(
370            $formItem.clone()
371                .append($formLabel.clone().text(settings.translations.openIn).attr("for", "openIn"))
372                .append(
373                    $formInputSelect
374                        .clone().attr("id", "openIn")
375                        .append($("<option />", {value: '_self', text: settings.translations.sameTab}))
376                        .append($("<option />", {value: '_blank', text: settings.translations.newTab}))
377                )
378        );
379        $linksForm.append($formItem.clone().append($formButton.clone()));
380        $linksDropdown.append($linksForm);
381        $btnURLs.append($dropdownOuter.clone().append($linksDropdown.prepend($dropdownClose.clone())));
382
383        /* box dropdown for video embedding */
384        var $videoDropdown = $dropdownBox.clone();
385        var $videoForm = $form.clone().attr("id", "richText-Video").attr("data-editor", editorID);
386        $videoForm.append(
387            $formItem.clone()
388                .append($formLabel.clone().text(settings.translations.url).attr("for", "videoURL"))
389                .append($formInput.clone().attr("id", "videoURL"))
390        );
391        $videoForm.append(
392            $formItem.clone()
393                .append($formLabel.clone().text(settings.translations.size).attr("for", "size"))
394                .append(
395                    $formInputSelect
396                        .clone().attr("id", "size")
397                        .append($("<option />", {value: 'responsive', text: settings.translations.responsive}))
398                        .append($("<option />", {value: '640x360', text: '640x360'}))
399                        .append($("<option />", {value: '560x315', text: '560x315'}))
400                        .append($("<option />", {value: '480x270', text: '480x270'}))
401                        .append($("<option />", {value: '320x180', text: '320x180'}))
402                )
403        );
404        $videoForm.append($formItem.clone().append($formButton.clone()));
405        $videoDropdown.append($videoForm);
406        $btnVideoEmbed.append($dropdownOuter.clone().append($videoDropdown.prepend($dropdownClose.clone())));
407
408        /* box dropdown for image upload/image selection */
409        var $imageDropdown = $dropdownBox.clone();
410        var $imageForm = $form.clone().attr("id", "richText-Image").attr("data-editor", editorID);
411
412        if (settings.imageHTML
413            && ($(settings.imageHTML).find('#imageURL').length > 0 || $(settings.imageHTML).attr("id") === "imageURL")) {
414            // custom image form
415            $imageForm.html(settings.imageHTML);
416        } else {
417            // default image form
418            $imageForm.append(
419                $formItem.clone()
420                    .append($formLabel.clone().text(settings.translations.imageURL).attr("for", "imageURL"))
421                    .append($formInput.clone().attr("id", "imageURL"))
422            );
423            $imageForm.append(
424                $formItem.clone()
425                    .append($formLabel.clone().text(settings.translations.align).attr("for", "align"))
426                    .append(
427                        $formInputSelect
428                            .clone().attr("id", "align")
429                            .append($("<option />", {value: 'left', text: settings.translations.left}))
430                            .append($("<option />", {value: 'center', text: settings.translations.center}))
431                            .append($("<option />", {value: 'right', text: settings.translations.right}))
432                    )
433            );
434        }
435        $imageForm.append($formItem.clone().append($formButton.clone()));
436        $imageDropdown.append($imageForm);
437        $btnImageUpload.append($dropdownOuter.clone().append($imageDropdown.prepend($dropdownClose.clone())));
438
439        /* box dropdown for file upload/file selection */
440        var $fileDropdown = $dropdownBox.clone();
441        var $fileForm = $form.clone().attr("id", "richText-File").attr("data-editor", editorID);
442
443        if (settings.fileHTML
444            && ($(settings.fileHTML).find('#fileURL').length > 0 || $(settings.fileHTML).attr("id") === "fileURL")) {
445            // custom file form
446            $fileForm.html(settings.fileHTML);
447        } else {
448            // default file form
449            $fileForm.append(
450                $formItem.clone()
451                    .append($formLabel.clone().text(settings.translations.fileURL).attr("for", "fileURL"))
452                    .append($formInput.clone().attr("id", "fileURL"))
453            );
454            $fileForm.append(
455                $formItem.clone()
456                    .append($formLabel.clone().text(settings.translations.linkText).attr("for", "fileText"))
457                    .append($formInput.clone().attr("id", "fileText"))
458            );
459        }
460        $fileForm.append($formItem.clone().append($formButton.clone()));
461        $fileDropdown.append($fileForm);
462        $btnFileUpload.append($dropdownOuter.clone().append($fileDropdown.prepend($dropdownClose.clone())));
463
464        /* box dropdown for tables */
465        var $tableDropdown = $dropdownBox.clone();
466        var $tableForm = $form.clone().attr("id", "richText-Table").attr("data-editor", editorID);
467        $tableForm.append(
468            $formItem.clone()
469                .append($formLabel.clone().text(settings.translations.rows).attr("for", "tableRows"))
470                .append($formInput.clone().attr("id", "tableRows").attr("type", "number"))
471        );
472        $tableForm.append(
473            $formItem.clone()
474                .append($formLabel.clone().text(settings.translations.columns).attr("for", "tableColumns"))
475                .append($formInput.clone().attr("id", "tableColumns").attr("type", "number"))
476        );
477        $tableForm.append($formItem.clone().append($formButton.clone()));
478        $tableDropdown.append($tableForm);
479        $btnTable.append($dropdownOuter.clone().append($tableDropdown.prepend($dropdownClose.clone())));
480
481
482        /* initizalize editor */
483        function init() {
484            var value, attributes, attributes_html = '';
485
486            if (settings.useParagraph !== false) {
487                // set default tag when pressing ENTER to <p> instead of <div>
488                document.execCommand("DefaultParagraphSeparator", false, 'p');
489            }
490
491            // reformat $inputElement to textarea
492            if ($inputElement.prop("tagName") === "TEXTAREA") {
493                // everything perfect
494            } else if ($inputElement.val()) {
495                value = $inputElement.val();
496                attributes = $inputElement.prop("attributes");
497                // loop through <select> attributes and apply them on <div>
498                $.each(attributes, function () {
499                    if (this.name) {
500                        attributes_html += ' ' + this.name + '="' + this.value + '"';
501                    }
502                });
503                $inputElement.replaceWith($('<textarea' + attributes_html + ' data-richtext="init">' + value + '</textarea>'));
504                $inputElement = $('[data-richtext="init"]');
505                $inputElement.removeAttr("data-richtext");
506            } else if ($inputElement.html()) {
507                value = $inputElement.html();
508                attributes = $inputElement.prop("attributes");
509                // loop through <select> attributes and apply them on <div>
510                $.each(attributes, function () {
511                    if (this.name) {
512                        attributes_html += ' ' + this.name + '="' + this.value + '"';
513                    }
514                });
515                $inputElement.replaceWith($('<textarea' + attributes_html + ' data-richtext="init">' + value + '</textarea>'));
516                $inputElement = $('[data-richtext="init"]');
517                $inputElement.removeAttr("data-richtext");
518            } else {
519                attributes = $inputElement.prop("attributes");
520                // loop through <select> attributes and apply them on <div>
521                $.each(attributes, function () {
522                    if (this.name) {
523                        attributes_html += ' ' + this.name + '="' + this.value + '"';
524                    }
525                });
526                $inputElement.replaceWith($('<textarea' + attributes_html + ' data-richtext="init"></textarea>'));
527                $inputElement = $('[data-richtext="init"]');
528                $inputElement.removeAttr("data-richtext");
529            }
530
531            $editor = $('<div />', {class: "richText"});
532            var $toolbar = $('<div />', {class: "richText-toolbar"});
533            var $editorView = $('<div />', {class: "richText-editor", id: editorID, contenteditable: true});
534            $toolbar.append($toolbarList);
535
536            /* text formatting */
537            if (settings.bold === true) {
538                $toolbarList.append($toolbarElement.clone().append($btnBold));
539            }
540            if (settings.italic === true) {
541                $toolbarList.append($toolbarElement.clone().append($btnItalic));
542            }
543            if (settings.underline === true) {
544                $toolbarList.append($toolbarElement.clone().append($btnUnderline));
545            }
546
547            /* align */
548            if (settings.leftAlign === true) {
549                $toolbarList.append($toolbarElement.clone().append($btnLeftAlign));
550            }
551            if (settings.centerAlign === true) {
552                $toolbarList.append($toolbarElement.clone().append($btnCenterAlign));
553            }
554            if (settings.rightAlign === true) {
555                $toolbarList.append($toolbarElement.clone().append($btnRightAlign));
556            }
557            if (settings.justify === true) {
558                $toolbarList.append($toolbarElement.clone().append($btnJustify));
559            }
560
561            /* lists */
562            if (settings.ol === true) {
563                $toolbarList.append($toolbarElement.clone().append($btnOL));
564            }
565            if (settings.ul === true) {
566                $toolbarList.append($toolbarElement.clone().append($btnUL));
567            }
568
569            /* fonts */
570            if (settings.fonts === true && settings.fontList.length > 0) {
571                $toolbarList.append($toolbarElement.clone().append($btnFont));
572            }
573            if (settings.fontSize === true) {
574                $toolbarList.append($toolbarElement.clone().append($btnFontSize));
575            }
576
577            /* heading */
578            if (settings.heading === true) {
579                $toolbarList.append($toolbarElement.clone().append($btnHeading));
580            }
581
582            /* colors */
583            if (settings.fontColor === true) {
584                $toolbarList.append($toolbarElement.clone().append($btnFontColor));
585            }
586
587            /* uploads */
588            if (settings.imageUpload === true) {
589                $toolbarList.append($toolbarElement.clone().append($btnImageUpload));
590            }
591            if (settings.fileUpload === true) {
592                $toolbarList.append($toolbarElement.clone().append($btnFileUpload));
593            }
594
595            /* media */
596            if (settings.videoEmbed === true) {
597                $toolbarList.append($toolbarElement.clone().append($btnVideoEmbed));
598            }
599
600            /* urls */
601            if (settings.urls === true) {
602                $toolbarList.append($toolbarElement.clone().append($btnURLs));
603            }
604
605            if (settings.table === true) {
606                $toolbarList.append($toolbarElement.clone().append($btnTable));
607            }
608
609            /* code */
610            if (settings.removeStyles === true) {
611                $toolbarList.append($toolbarElement.clone().append($btnRemoveStyles));
612            }
613            if (settings.code === true) {
614                $toolbarList.append($toolbarElement.clone().append($btnCode));
615            }
616
617            // set current textarea value to editor
618            $editorView.html($inputElement.val());
619
620            $editor.append($toolbar);
621            $editor.append($editorView);
622            $editor.append($inputElement.clone().hide());
623            $inputElement.replaceWith($editor);
624
625            // append bottom toolbar
626            // $editor.append(
627            //     $('<div />', {class: 'richText-toolbar'})
628            //         .append($('<a />', {
629            //             class: 'richText-undo is-disabled',
630            //             html: '<span class="fa fa-undo"></span>',
631            //             'title': settings.translations.undo
632            //         }))
633            //         .append($('<a />', {
634            //             class: 'richText-redo is-disabled',
635            //             html: '<span class="fa fa-repeat fa-redo"></span>',
636            //             'title': settings.translations.redo
637            //         }))
638            //         .append($('<a />', {class: 'richText-help', html: '<span class="fa fa-question-circle"></span>'}))
639            // );
640
641            if (settings.maxlength > 0) {
642                // display max length in editor toolbar
643                $editor.data('maxlength', settings.maxlength);
644                $editor.children('.richText-toolbar').children('.richText-help').before($('<a />', {
645                    class: 'richText-length',
646                    text: '0/' + settings.maxlength
647                }));
648            }
649
650            if (settings.height && settings.height > 0) {
651                // set custom editor height
652                $editor.children(".richText-editor, .richText-initial").css({
653                    'min-height': settings.height + 'px',
654                    'height': settings.height + 'px'
655                });
656            } else if (settings.heightPercentage && settings.heightPercentage > 0) {
657                // set custom editor height in percentage
658                var parentHeight = $editor.parent().innerHeight(); // get editor parent height
659                var height = (settings.heightPercentage / 100) * parentHeight; // calculate pixel value from percentage
660                height -= $toolbar.outerHeight() * 2; // remove toolbar size
661                height -= parseInt($editor.css("margin-top")); // remove margins
662                height -= parseInt($editor.css("margin-bottom")); // remove margins
663                height -= parseInt($editor.find(".richText-editor").css("padding-top")); // remove paddings
664                height -= parseInt($editor.find(".richText-editor").css("padding-bottom")); // remove paddings
665                $editor.children(".richText-editor, .richText-initial").css({
666                    'min-height': height + 'px',
667                    'height': height + 'px'
668                });
669            }
670
671            // add custom class
672            if (settings.class) {
673                $editor.addClass(settings.class);
674            }
675            if (settings.id) {
676                $editor.attr("id", settings.id);
677            }
678
679            // fix the first line
680            fixFirstLine();
681
682            // save history
683            history[editorID].push($editor.find("textarea").val());
684
685            if (settings.callback && typeof settings.callback === 'function') {
686                settings.callback($editor);
687            }
688        }
689
690        // initialize editor
691        init();
692
693
694        /** EVENT HANDLERS */
695
696        // Help popup
697        $editor.find(".richText-help").on("click", function () {
698            var $editor = $(this).parents(".richText");
699            if ($editor) {
700                var $outer = $('<div />', {
701                    class: 'richText-help-popup',
702                    style: 'position:absolute;top:0;right:0;bottom:0;left:0;background-color: rgba(0,0,0,0.3);'
703                });
704                var $inner = $('<div />', {style: 'position:relative;margin:60px auto;padding:20px;background-color:#FAFAFA;width:70%;font-family:Calibri,Verdana,Helvetica,sans-serif;font-size:small;'});
705                var $content = $('<div />', {html: '<span id="closeHelp" style="display:block;position:absolute;top:0;right:0;padding:10px;cursor:pointer;" title="' + settings.translations.close + '"><span class="fa fa-times"></span></span>'});
706                $content.append('<h3 style="margin:0;">RichText</h3>');
707                $content.append('<hr><br>Powered by <a href="https://github.com/webfashionist/RichText" target="_blank">webfashionist/RichText</a> (Github) <br>License: <a href="https://github.com/webfashionist/RichText/blob/master/LICENSE" target="_blank">AGPL-3.0</a>');
708
709                $outer.append($inner.append($content));
710                $editor.append($outer);
711
712                $outer.on("click", "#closeHelp", function () {
713                    $(this).parents('.richText-help-popup').remove();
714                });
715            }
716        });
717
718        // undo / redo
719        $(document).on("click", ".richText-undo, .richText-redo", function (e) {
720            var $this = $(this);
721            var $editor = $this.parents('.richText');
722            if ($this.hasClass("richText-undo") && !$this.hasClass("is-disabled")) {
723                undo($editor);
724            } else if ($this.hasClass("richText-redo") && !$this.hasClass("is-disabled")) {
725                redo($editor);
726            }
727        });
728
729
730        // Saving changes from editor to textarea
731        $(document).on("input change blur keydown keyup", ".richText-editor", function (e) {
732            if ((e.keyCode === 9 || e.keyCode === "9") && e.type === "keydown") {
733                // tab through table cells
734                e.preventDefault();
735                tabifyEditableTable(window, e);
736                return false;
737            }
738            fixFirstLine();
739            updateTextarea();
740            doSave($(this).attr("id"));
741            updateMaxLength($(this).attr('id'));
742        });
743
744
745        // add context menu to several Node elements
746        $(document).on('contextmenu', '.richText-editor', function (e) {
747
748            var $list = $('<ul />', {'class': 'list-rightclick richText-list'});
749            var $li = $('<li />');
750            // remove Node selection
751            $('.richText-editor').find('.richText-editNode').removeClass('richText-editNode');
752
753            var $target = $(e.target);
754            var $richText = $target.parents('.richText');
755            var $toolbar = $richText.find('.richText-toolbar');
756
757            var positionX = e.pageX - $richText.offset().left;
758            var positionY = e.pageY - $richText.offset().top;
759
760            $list.css({
761                'top': positionY,
762                'left': positionX
763            });
764
765
766            if ($target.prop("tagName") === "A") {
767                // edit URL
768                e.preventDefault();
769
770                $list.append($li.clone().html('<span class="fa fa-link"></span>'));
771                $target.parents('.richText').append($list);
772                $list.find('.fa-link').on('click', function () {
773                    $('.list-rightclick.richText-list').remove();
774                    $target.addClass('richText-editNode');
775                    var $popup = $toolbar.find('#richText-URL');
776                    $popup.find('input#url').val($target.attr('href'));
777                    $popup.find('input#urlText').val($target.text());
778                    $popup.find('select#openIn').val($target.attr('target'));
779                    $toolbar.find('.richText-btn').children('.fa-link').parents('li').addClass('is-selected');
780                });
781
782                return false;
783            } else if ($target.prop("tagName") === "IMG") {
784                // edit image
785                e.preventDefault();
786
787                $list.append($li.clone().html('<span class="fa fa-image"></span>'));
788                $target.parents('.richText').append($list);
789                $list.find('.fa-image').on('click', function () {
790                    var align;
791                    if ($target.parent('div').length > 0 && $target.parent('div').attr('style') === 'text-align:center;') {
792                        align = 'center';
793                    } else {
794                        align = $target.attr('align');
795                    }
796                    $('.list-rightclick.richText-list').remove();
797                    $target.addClass('richText-editNode');
798                    var $popup = $toolbar.find('#richText-Image');
799                    $popup.find('input#imageURL').val($target.attr('src'));
800                    $popup.find('select#align').val(align);
801                    $toolbar.find('.richText-btn').children('.fa-image').parents('li').addClass('is-selected');
802                });
803
804                return false;
805            }
806
807        });
808
809        // Saving changes from textarea to editor
810        $(document).on("input change blur", ".richText-initial", function () {
811            if (settings.useSingleQuotes === true) {
812                $(this).val(changeAttributeQuotes($(this).val()));
813            }
814            var editorID = $(this).siblings('.richText-editor').attr("id");
815            updateEditor(editorID);
816            doSave(editorID);
817            updateMaxLength(editorID);
818        });
819
820        // Save selection seperately (mainly needed for Safari)
821        $(document).on("dblclick mouseup", ".richText-editor", function () {
822            var editorID = $(this).attr("id");
823            doSave(editorID);
824        });
825
826        // embedding video
827        $(document).on("click", "#richText-Video button.btn", function (event) {
828            event.preventDefault();
829            var $button = $(this);
830            var $form = $button.parent('.richText-form-item').parent('.richText-form');
831            if ($form.attr("data-editor") === editorID) {
832                // only for the currently selected editor
833                var url = $form.find('input#videoURL').val();
834                var size = $form.find('select#size').val();
835
836                if (!url) {
837                    // no url set
838                    $form.prepend($('<div />', {
839                        style: 'color:red;display:none;',
840                        class: 'form-item is-error',
841                        text: settings.translations.pleaseEnterURL
842                    }));
843                    $form.children('.form-item.is-error').slideDown();
844                    setTimeout(function () {
845                        $form.children('.form-item.is-error').slideUp(function () {
846                            $(this).remove();
847                        });
848                    }, 5000);
849                } else {
850                    // write html in editor
851                    var html = '';
852                    html = getVideoCode(url, size);
853                    if (!html) {
854                        $form.prepend($('<div />', {
855                            style: 'color:red;display:none;',
856                            class: 'form-item is-error',
857                            text: settings.translations.videoURLnotSupported
858                        }));
859                        $form.children('.form-item.is-error').slideDown();
860                        setTimeout(function () {
861                            $form.children('.form-item.is-error').slideUp(function () {
862                                $(this).remove();
863                            });
864                        }, 5000);
865                    } else {
866                        if (settings.useSingleQuotes === true) {
867
868                        } else {
869
870                        }
871                        restoreSelection(editorID, true);
872                        pasteHTMLAtCaret(html);
873                        updateTextarea();
874                        // reset input values
875                        $form.find('input#videoURL').val('');
876                        $('.richText-toolbar li.is-selected').removeClass("is-selected");
877                    }
878                }
879            }
880        });
881
882        // Resize images
883        $(document).on('mousedown', function (e) {
884            var $target = $(e.target);
885            if (!$target.hasClass('richText-list') && $target.parents('.richText-list').length === 0) {
886                // remove context menu
887                $('.richText-list.list-rightclick').remove();
888                if (!$target.hasClass('richText-form') && $target.parents('.richText-form').length === 0) {
889                    $('.richText-editNode').each(function () {
890                        var $this = $(this);
891                        $this.removeClass('richText-editNode');
892                        if ($this.attr('class') === '') {
893                            $this.removeAttr('class');
894                        }
895                    });
896                }
897            }
898            if ($target.prop("tagName") === "IMG" && $target.parents("#" + editorID)) {
899                startX = e.pageX;
900                startY = e.pageY;
901                startW = $target.innerWidth();
902                startH = $target.innerHeight();
903
904                var left = $target.offset().left;
905                var right = $target.offset().left + $target.innerWidth();
906                var bottom = $target.offset().top + $target.innerHeight();
907                var top = $target.offset().top;
908                var resize = false;
909                $target.css({'cursor': 'default'});
910
911                if (startY <= bottom && startY >= bottom - 20 && startX >= right - 20 && startX <= right) {
912                    // bottom right corner
913                    $resizeImage = $target;
914                    $resizeImage.css({'cursor': 'nwse-resize'});
915                    resize = true;
916                }
917
918                if ((resize === true || $resizeImage) && !$resizeImage.data("width")) {
919                    // set initial image size and prevent dragging image while resizing
920                    $resizeImage.data("width", $target.parents("#" + editorID).innerWidth());
921                    $resizeImage.data("height", $target.parents("#" + editorID).innerHeight() * 3);
922                    e.preventDefault();
923                } else if (resize === true || $resizeImage) {
924                    // resizing active, prevent other events
925                    e.preventDefault();
926                } else {
927                    // resizing disabled, allow dragging image
928                    $resizeImage = null;
929                }
930
931            }
932        });
933        $(document)
934            .mouseup(function () {
935                if ($resizeImage) {
936                    $resizeImage.css({'cursor': 'default'});
937                }
938                $resizeImage = null;
939            })
940            .mousemove(function (e) {
941                if ($resizeImage !== null) {
942                    var maxWidth = $resizeImage.data('width');
943                    var currentWidth = $resizeImage.width();
944                    var maxHeight = $resizeImage.data('height');
945                    var currentHeight = $resizeImage.height();
946                    if ((startW + e.pageX - startX) <= maxWidth && (startH + e.pageY - startY) <= maxHeight) {
947                        // only resize if new size is smaller than the original image size
948                        $resizeImage.innerWidth(startW + e.pageX - startX); // only resize width to adapt height proportionally
949                        // $box.innerHeight(startH + e.pageY-startY);
950                        updateTextarea();
951                    } else if ((startW + e.pageX - startX) <= currentWidth && (startH + e.pageY - startY) <= currentHeight) {
952                        // only resize if new size is smaller than the previous size
953                        $resizeImage.innerWidth(startW + e.pageX - startX); // only resize width to adapt height proportionally
954                        updateTextarea();
955                    }
956                }
957            });
958
959        // adding URL
960        $(document).on("click", "#richText-URL button.btn", function (event) {
961            event.preventDefault();
962            var $button = $(this);
963            var $form = $button.parent('.richText-form-item').parent('.richText-form');
964            if ($form.attr("data-editor") === editorID) {
965                // only for currently selected editor
966                var url = $form.find('input#url').val();
967                var text = $form.find('input#urlText').val();
968                var target = $form.find('#openIn').val();
969
970                // set default values
971                if (!target) {
972                    target = '_self';
973                }
974                if (!text) {
975                    text = url;
976                }
977                if (!url) {
978                    // no url set
979                    $form.prepend($('<div />', {
980                        style: 'color:red;display:none;',
981                        class: 'form-item is-error',
982                        text: settings.translations.pleaseEnterURL
983                    }));
984                    $form.children('.form-item.is-error').slideDown();
985                    setTimeout(function () {
986                        $form.children('.form-item.is-error').slideUp(function () {
987                            $(this).remove();
988                        });
989                    }, 5000);
990                } else {
991                    // write html in editor
992                    var html = '';
993                    if (settings.useSingleQuotes === true) {
994                        html = "<a href='" + url + "' target='" + target + "'>" + text + "</a>";
995                    } else {
996                        html = '<a href="' + url + '" target="' + target + '">' + text + '</a>';
997                    }
998                    restoreSelection(editorID, false, true);
999
1000                    var $editNode = $('.richText-editNode');
1001                    if ($editNode.length > 0 && $editNode.prop("tagName") === "A") {
1002                        $editNode.attr("href", url);
1003                        $editNode.attr("target", target);
1004                        $editNode.text(text);
1005                        $editNode.removeClass('richText-editNode');
1006                        if ($editNode.attr('class') === '') {
1007                            $editNode.removeAttr('class');
1008                        }
1009                    } else {
1010                        pasteHTMLAtCaret(html);
1011                    }
1012                    // reset input values
1013                    $form.find('input#url').val('');
1014                    $form.find('input#urlText').val('');
1015                    $('.richText-toolbar li.is-selected').removeClass("is-selected");
1016                }
1017            }
1018        });
1019
1020        // adding image
1021        $(document).on("click", "#richText-Image button.btn", function (event) {
1022            event.preventDefault();
1023            var $button = $(this);
1024            var $form = $button.parent('.richText-form-item').parent('.richText-form');
1025            if ($form.attr("data-editor") === editorID) {
1026                // only for currently selected editor
1027                var url = $form.find('#imageURL').val();
1028                var align = $form.find('select#align').val();
1029
1030                // set default values
1031                if (!align) {
1032                    align = 'center';
1033                }
1034                if (!url) {
1035                    // no url set
1036                    $form.prepend($('<div />', {
1037                        style: 'color:red;display:none;',
1038                        class: 'form-item is-error',
1039                        text: settings.translations.pleaseSelectImage
1040                    }));
1041                    $form.children('.form-item.is-error').slideDown();
1042                    setTimeout(function () {
1043                        $form.children('.form-item.is-error').slideUp(function () {
1044                            $(this).remove();
1045                        });
1046                    }, 5000);
1047                } else {
1048                    // write html in editor
1049                    var html = '';
1050                    if (settings.useSingleQuotes === true) {
1051                        if (align === "center") {
1052                            html = "<div style='text-align:center;'><img src='" + url + "'></div>";
1053                        } else {
1054                            html = "<img src='" + url + "' align='" + align + "'>";
1055                        }
1056                    } else {
1057                        if (align === "center") {
1058                            html = '<div style="text-align:center;"><img src="' + url + '"></div>';
1059                        } else {
1060                            html = '<img src="' + url + '" align="' + align + '">';
1061                        }
1062                    }
1063                    restoreSelection(editorID, true);
1064                    var $editNode = $('.richText-editNode');
1065                    if ($editNode.length > 0 && $editNode.prop("tagName") === "IMG") {
1066                        $editNode.attr("src", url);
1067                        if ($editNode.parent('div').length > 0 && $editNode.parent('div').attr('style') === 'text-align:center;' && align !== 'center') {
1068                            $editNode.unwrap('div');
1069                            $editNode.attr('align', align);
1070                        } else if (($editNode.parent('div').length === 0 || $editNode.parent('div').attr('style') !== 'text-align:center;') && align === 'center') {
1071                            $editNode.wrap('<div style="text-align:center;"></div>');
1072                            $editNode.removeAttr('align');
1073                        } else {
1074                            $editNode.attr('align', align);
1075                        }
1076                        $editNode.removeClass('richText-editNode');
1077                        if ($editNode.attr('class') === '') {
1078                            $editNode.removeAttr('class');
1079                        }
1080                    } else {
1081                        pasteHTMLAtCaret(html);
1082                    }
1083                    // reset input values
1084                    $form.find('input#imageURL').val('');
1085                    $('.richText-toolbar li.is-selected').removeClass("is-selected");
1086                }
1087            }
1088        });
1089
1090        // adding file
1091        $(document).on("click", "#richText-File button.btn", function (event) {
1092            event.preventDefault();
1093            var $button = $(this);
1094            var $form = $button.parent('.richText-form-item').parent('.richText-form');
1095            if ($form.attr("data-editor") === editorID) {
1096                // only for currently selected editor
1097                var url = $form.find('#fileURL').val();
1098                var text = $form.find('#fileText').val();
1099
1100                // set default values
1101                if (!text) {
1102                    text = url;
1103                }
1104                if (!url) {
1105                    // no url set
1106                    $form.prepend($('<div />', {
1107                        style: 'color:red;display:none;',
1108                        class: 'form-item is-error',
1109                        text: settings.translations.pleaseSelectFile
1110                    }));
1111                    $form.children('.form-item.is-error').slideDown();
1112                    setTimeout(function () {
1113                        $form.children('.form-item.is-error').slideUp(function () {
1114                            $(this).remove();
1115                        });
1116                    }, 5000);
1117                } else {
1118                    // write html in editor
1119                    var html = '';
1120                    if (settings.useSingleQuotes === true) {
1121                        html = "<a href='" + url + "' target='_blank'>" + text + "</a>";
1122                    } else {
1123                        html = '<a href="' + url + '" target="_blank">' + text + '</a>';
1124                    }
1125                    restoreSelection(editorID, true);
1126                    pasteHTMLAtCaret(html);
1127                    // reset input values
1128                    $form.find('input#fileURL').val('');
1129                    $form.find('input#fileText').val('');
1130                    $('.richText-toolbar li.is-selected').removeClass("is-selected");
1131                }
1132            }
1133        });
1134
1135
1136        // adding table
1137        $(document).on("click", "#richText-Table button.btn", function (event) {
1138            event.preventDefault();
1139            var $button = $(this);
1140            var $form = $button.parent('.richText-form-item').parent('.richText-form');
1141            if ($form.attr("data-editor") === editorID) {
1142                // only for currently selected editor
1143                var rows = $form.find('input#tableRows').val();
1144                var columns = $form.find('input#tableColumns').val();
1145
1146                // set default values
1147                if (!rows || rows <= 0) {
1148                    rows = 2;
1149                }
1150                if (!columns || columns <= 0) {
1151                    columns = 2;
1152                }
1153
1154                // generate table
1155                var html = '';
1156                if (settings.useSingleQuotes === true) {
1157                    html = "<table class='table-1'><tbody>";
1158                } else {
1159                    html = '<table class="table-1"><tbody>';
1160                }
1161                for (var i = 1; i <= rows; i++) {
1162                    // start new row
1163                    html += '<tr>';
1164                    for (var n = 1; n <= columns; n++) {
1165                        // start new column in row
1166                        html += '<td> </td>';
1167                    }
1168                    html += '</tr>';
1169                }
1170                html += '</tbody></table>';
1171
1172                // write html in editor
1173                restoreSelection(editorID, true);
1174                pasteHTMLAtCaret(html);
1175                // reset input values
1176                $form.find('input#tableColumns').val('');
1177                $form.find('input#tableRows').val('');
1178                $('.richText-toolbar li.is-selected').removeClass("is-selected");
1179            }
1180        });
1181
1182        // opening / closing toolbar dropdown
1183        $(document).on("click", function (event) {
1184            var $clickedElement = $(event.target);
1185
1186            if ($clickedElement.parents('.richText-toolbar').length === 0) {
1187                // element not in toolbar
1188                // ignore
1189            } else if ($clickedElement.hasClass("richText-dropdown-outer")) {
1190                // closing dropdown by clicking inside the editor
1191                $clickedElement.parent('a').parent('li').removeClass("is-selected");
1192            } else if ($clickedElement.find(".richText").length > 0) {
1193                // closing dropdown by clicking outside of the editor
1194                $('.richText-toolbar li').removeClass("is-selected");
1195            } else if ($clickedElement.parent().hasClass("richText-dropdown-close")) {
1196                // closing dropdown by clicking on the close button
1197                $('.richText-toolbar li').removeClass("is-selected");
1198            } else if ($clickedElement.hasClass("richText-btn") && $(event.target).children('.ric
1198hText-dropdown-outer').length > 0) {
1199                // opening dropdown by clicking on toolbar button
1200                $clickedElement.parent('li').addClass("is-selected");
1201
1202                if ($clickedElement.children('.fa,svg').hasClass("fa-link")) {
1203                    // put currently selected text in URL form to replace it
1204                    restoreSelection(editorID, false, true);
1205                    var selectedText = getSelectedText();
1206                    $clickedElement.find("input#urlText").val('');
1207                    $clickedElement.find("input#url").val('');
1208                    if (selectedText) {
1209                        $clickedElement.find("input#urlText").val(selectedText);
1210                    }
1211                } else if ($clickedElement.hasClass("fa-image")) {
1212                    // image
1213                }
1214            }
1215        });
1216
1217        // Executing editor commands
1218        $(document).on("click", ".richText-toolbar a[data-command]", function (event) {
1219            var $button = $(this);
1220            var $toolbar = $button.closest('.richText-toolbar');
1221            var $editor = $toolbar.siblings('.richText-editor');
1222            var id = $editor.attr("id");
1223            if ($editor.length > 0 && id === editorID && (!$button.parent("li").attr('data-disable') || $button.parent("li").attr('data-disable') === "false")) {
1224                event.preventDefault();
1225                var command = $(this).data("command");
1226
1227                if (command === "toggleCode") {
1228                    toggleCode($editor.attr("id"));
1229                } else {
1230                    var option = null;
1231                    if ($(this).data('option')) {
1232                        option = $(this).data('option').toString();
1233                        if (option.match(/^h[1-6]$/)) {
1234                            command = "heading";
1235                        }
1236                    }
1237
1238                    formatText(command, option, id);
1239                    if (command === "removeFormat") {
1240                        // remove HTML/CSS formatting
1241                        $editor.find('*').each(function () {
1242                            // remove all, but very few, attributes from the nodes
1243                            var keepAttributes = [
1244                                "id", "class",
1245                                "name", "action", "method",
1246                                "src", "align", "alt", "title",
1247                                "style", "webkitallowfullscreen", "mozallowfullscreen", "allowfullscreen",
1248                                "width", "height", "frameborder"
1249                            ];
1250                            var element = $(this);
1251                            var attributes = $.map(this.attributes, function (item) {
1252                                return item.name;
1253                            });
1254                            $.each(attributes, function (i, item) {
1255                                if (keepAttributes.indexOf(item) < 0 && item.substr(0, 5) !== 'data-') {
1256                                    element.removeAttr(item);
1257                                }
1258                            });
1259                            if (element.prop('tagName') === "A") {
1260                                // remove empty URL tags
1261                                element.replaceWith(function () {
1262                                    return $('<span />', {html: $(this).html()});
1263                                });
1264                            }
1265                        });
1266                        formatText('formatBlock', 'div', id);
1267                    }
1268                    // clean up empty tags, which can be created while replacing formatting or when copy-
1268pasting from other tools
1269                    $editor.find('div:empty,p:empty,li:empty,h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty').remove();
1270                    $editor.find('h1,h2,h3,h4,h5,h6').unwrap('h1,h2,h3,h4,h5,h6');
1271                }
1272            }
1273            // close dropdown after click
1274            $button.parents('li.is-selected').removeClass('is-selected');
1275        });
1276
1277
1278        /** INTERNAL METHODS **/
1279
1280        /**
1281         * Format text in editor
1282         * @param {string} command
1283         * @param {string|null} option
1284         * @param {string} editorID
1285         * @private
1286         */
1287        function formatText(command, option, editorID) {
1288            if (typeof option === "undefined") {
1289                option = null;
1290            }
1291            // restore selection from before clicking on any button
1292            doRestore(editorID);
1293            // Temporarily enable designMode so that
1294            // document.execCommand() will work
1295            // document.designMode = "ON";
1296            // Execute the command
1297            if (command === "heading" && getSelectedText()) {
1298                // IE workaround
1299                pasteHTMLAtCaret('<' + option + '>' + getSelectedText() + '</' + option + '>');
1300            } else if (command === "fontSize" && parseInt(option) > 0) {
1301                var selection = getSelectedText();
1302                selection = (selection + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br>' + '$2');
1303                var html = (settings.useSingleQuotes ? "<span style='font-size:" + option + "px;'>" + selection + "</span>" : '<span style="font-size:' + option + 'px;">' + selection + '</span>');
1304                pasteHTMLAtCaret(html);
1305            } else {
1306                document.execCommand(command, false, option);
1307            }
1308            // Disable designMode
1309            // document.designMode = "OFF";
1310        }
1311
1312
1313        /**
1314         * Update textarea when updating editor
1315         * @private
1316         */
1317        function updateTextarea() {
1318            var $editor = $('#' + editorID);
1319            var content = $editor.html();
1320            if (settings.useSingleQuotes === true) {
1321                content = changeAttributeQuotes(content);
1322            }
1323            $editor.siblings('.richText-initial').val(content);
1324        }
1325
1326
1327        /**
1328         * Update editor when updating textarea
1329         * @private
1330         */
1331        function updateEditor(editorID) {
1332            var $editor = $('#' + editorID);
1333            var content = $editor.siblings('.richText-initial').val();
1334            $editor.html(content);
1335        }
1336
1337
1338        /**
1339         * Save caret position and selection
1340         * @return object
1341         **/
1342        function saveSelection(editorID) {
1343            var containerEl = document.getElementById(editorID);
1344            var range, start, end, type;
1345            if (window.getSelection && document.createRange) {
1346                var sel = window.getSelection && window.getSelection();
1347                if (sel && sel.rangeCount > 0 && $(sel.anchorNode).parents('#' + editorID).length > 0) {
1348                    range = window.getSelection().getRangeAt(0);
1349                    var preSelectionRange = range.cloneRange();
1350                    preSelectionRange.selectNodeContents(containerEl);
1351                    preSelectionRange.setEnd(range.startContainer, range.startOffset);
1352
1353                    start = preSelectionRange.toString().length;
1354                    end = (start + range.toString().length);
1355
1356                    type = (start === end ? 'caret' : 'selection');
1357                    anchor = sel.anchorNode; //(type === "caret" && sel.anchorNode.tagName ? sel.anchorNode : false);
1358                    start = (type === 'caret' && anchor !== false ? start : preSelectionRange.toString().length);
1359                    end = (type === 'caret' && anchor !== false ? end : (start + range.toString().length));
1360
1361                    return {
1362                        start: start,
1363                        end: end,
1364                        type: type,
1365                        anchor: anchor,
1366                        editorID: editorID
1367                    }
1368                }
1369            }
1370            return (savedSelection ? savedSelection : {
1371                start: 0,
1372                end: 0
1373            });
1374        }
1375
1376
1377        /**
1378         * Restore selection
1379         **/
1380        function restoreSelection(editorID, media, url) {
1381            var containerEl = document.getElementById(editorID);
1382            var savedSel = savedSelection;
1383            if (!savedSel) {
1384                // fix selection if editor has not been focused
1385                savedSel = {
1386                    'start': 0,
1387                    'end': 0,
1388                    'type': 'caret',
1389                    'editorID': editorID,
1390                    'anchor': $('#' + editorID).children('div')[0]
1391                };
1392            }
1393
1394            if (savedSel.editorID !== editorID) {
1395                return false;
1396            } else if (media === true) {
1397                containerEl = (savedSel.anchor ? savedSel.anchor : containerEl); // fix selection issue
1398            } else if (url === true) {
1399                if (savedSel.start === 0 && savedSel.end === 0) {
1400                    containerEl = (savedSel.anchor ? savedSel.anchor : containerEl); // fix selection issue
1401                }
1402            }
1403
1404            if (window.getSelection && document.createRange) {
1405                var charIndex = 0, range = document.createRange();
1406                if (!range || !containerEl) {
1407                    window.getSelection().removeAllRanges();
1408                    return true;
1409                }
1410                range.setStart(containerEl, 0);
1411                range.collapse(true);
1412                var nodeStack = [containerEl], node, foundStart = false, stop = false;
1413
1414                while (!stop && (node = nodeStack.pop())) {
1415                    if (node.nodeType === 3) {
1416                        var nextCharIndex = charIndex + node.length;
1417                        if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
1418                            range.setStart(node, savedSel.start - charIndex);
1419                            foundStart = true;
1420                        }
1421                        if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
1422                            range.setEnd(node, savedSel.end - charIndex);
1423                            stop = true;
1424                        }
1425                        charIndex = nextCharIndex;
1426                    } else {
1427                        var i = node.childNodes.length;
1428                        while (i--) {
1429                            nodeStack.push(node.childNodes[i]);
1430                        }
1431                    }
1432                }
1433                var sel = window.getSelection();
1434                sel.removeAllRanges();
1435                sel.addRange(range);
1436            }
1437        }
1438
1439
1440        /**
1441         * Save caret position and selection
1442         * @return object
1443         **/
1444        /*
1445       function saveSelection(editorID) {
1446           var containerEl = document.getElementById(editorID);
1447           var start;
1448           if (window.getSelection && document.createRange) {
1449               var sel = window.getSelection && window.getSelection();
1450               if (sel && sel.rangeCount > 0) {
1451                   var range = window.getSelection().getRangeAt(0);
1452                   var preSelectionRange = range.cloneRange();
1453                   preSelectionRange.selectNodeContents(containerEl);
1454                   preSelectionRange.setEnd(range.startContainer, range.startOffset);
1455                   start = preSelectionRange.toString().length;
1456
1457                   return {
1458                       start: start,
1459                       end: start + range.toString().length,
1460                       editorID: editorID
1461                   }
1462               } else {
1463                   return (savedSelection ? savedSelection : {
1464                       start: 0,
1465                       end: 0
1466                   });
1467               }
1468           } else if (document.selection && document.body.createTextRange) {
1469               var selectedTextRange = document.selection.createRange();
1470               var preSelectionTextRange = document.body.createTextRange();
1471               preSelectionTextRange.moveToElementText(containerEl);
1472               preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
1473               start = preSelectionTextRange.text.length;
1474
1475               return {
1476                   start: start,
1477                   end: start + selectedTextRange.text.length,
1478                   editorID: editorID
1479               };
1480           }
1481       }
1482       */
1483
1484        /**
1485         * Restore selection
1486         **/
1487
1488        /*
1489       function restoreSelection(editorID) {
1490           var containerEl = document.getElementById(editorID);
1491           var savedSel = savedSelection;
1492           if(savedSel.editorID !== editorID) {
1493               return false;
1494           }
1495           if (window.getSelection && document.createRange) {
1496               var charIndex = 0, range = document.createRange();
1497               range.setStart(containerEl, 0);
1498               range.collapse(true);
1499               var nodeStack = [containerEl], node, foundStart = false, stop = false;
1500
1501               while (!stop && (node = nodeStack.pop())) {
1502                   if (node.nodeType === 3) {
1503                       var nextCharIndex = charIndex + node.length;
1504                       if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
1505                           range.setStart(node, savedSel.start - charIndex);
1506                           foundStart = true;
1507                       }
1508                       if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
1509                           range.setEnd(node, savedSel.end - charIndex);
1510                           stop = true;
1511                       }
1512                       charIndex = nextCharIndex;
1513                   } else {
1514                       var i = node.childNodes.length;
1515                       while (i--) {
1516                           nodeStack.push(node.childNodes[i]);
1517                       }
1518                   }
1519               }
1520               var sel = window.getSelection();
1521               sel.removeAllRanges();
1522               sel.addRange(range);
1523           } else if (document.selection && document.body.createTextRange) {
1524               var textRange = document.body.createTextRange();
1525               textRange.moveToElementText(containerEl);
1526               textRange.collapse(true);
1527               textRange.moveEnd("character", savedSel.end);
1528               textRange.moveStart("character", savedSel.start);
1529               textRange.select();
1530           }
1531       }
1532       */
1533
1534        /**
1535         * Enables tabbing/shift-tabbing between contentEditable table cells
1536         * @param {Window} win - Active window context.
1537         * @param {Event} e - jQuery Event object for the keydown that fired.
1538         */
1539        function tabifyEditableTable(win, e) {
1540
1541            if (e.keyCode !== 9) {
1542                return false;
1543            }
1544
1545            var sel;
1546            if (win.getSelection) {
1547                sel = win.getSelection();
1548                if (sel.rangeCount > 0) {
1549
1550                    var textNode = null,
1551                        direction = null;
1552
1553                    if (!e.shiftKey) {
1554                        direction = "next";
1555                        textNode = (sel.focusNode.nodeName === "TD")
1556                            ? (sel.focusNode.nextSibling != null)
1557                                ? sel.focusNode.nextSibling
1558                                : (sel.focusNode.parentNode.nextSibling != null)
1559                                    ? sel.focusNode.parentNode.nextSibling.childNodes[0]
1560                                    : null
1561                            : (sel.focusNode.parentNode.nextSibling != null)
1562                                ? sel.focusNode.parentNode.nextSibling
1563                                : (sel.focusNode.parentNode.parentNode.nextSibling != null)
1564                                    ? sel.focusNode.parentNode.parentNode.nextSibling.childNodes[0]
1565                                    : null;
1566                    } else {
1567                        direction = "previous";
1568                        textNode = (sel.focusNode.nodeName === "TD")
1569                            ? (sel.focusNode.previousSibling != null)
1570                                ? sel.focusNode.previousSibling
1571                                : (sel.focusNode.parentNode.previousSibling != null)
1572                                    ? sel.focusNode.parentNode.previousSibling.childNodes[sel.focusNode.parentNode.previousSibling.childNodes.length - 1]
1573                                    : null
1574                            : (sel.focusNode.parentNode.previousSibling != null)
1575                                ? sel.focusNode.parentNode.previousSibling
1576                                : (sel.focusNode.parentNode.parentNode.previousSibling != null)
1577                                    ? sel.focusNode.parentNode.parentNode.previousSibling.childNodes[sel.focusNode.parentNode.parentNode.previousSibling.childNodes.length - 1]
1578                                    : null;
1579                    }
1580
1581                    if (textNode != null) {
1582                        sel.collapse(textNode, Math.min(textNode.length, sel.focusOffset + 1));
1583                        if (textNode.textContent != null) {
1584                            sel.selectAllChildren(textNode);
1585                        }
1586                        e.preventDefault();
1587                        return true;
1588                    } else if (textNode === null && direction === "next" && sel.focusNode.nodeName === "TD") {
1589                        // add new row on TAB if arrived at the end of the row
1590                        var $table = $(sel.focusNode).parents("table");
1591                        var cellsPerLine = $table.find("tr").first().children("td").length;
1592                        var $tr = $("<tr />");
1593                        var $td = $("<td />");
1594                        for (var i = 1; i <= cellsPerLine; i++) {
1595                            $tr.append($td.clone());
1596                        }
1597                        $table.append($tr);
1598                        // simulate tabing through table
1599                        tabifyEditableTable(window, {
1600                            keyCode: 9, shiftKey: false, preventDefault: function () {
1601                            }
1602                        });
1603                    }
1604                }
1605            }
1606            return false;
1607        }
1608
1609        /**
1610         * Returns the text from the current selection
1611         * @private
1612         * @return {string|boolean}
1613         */
1614        function getSelectedText() {
1615            var range;
1616            if (window.getSelection) {  // all browsers, except IE before version 9
1617                range = window.getSelection();
1618                return range.toString() ? range.toString() : range.focusNode.nodeValue;
1619            } else if (document.selection.createRange) { // Internet Explorer
1620                range = document.selection.createRange();
1621                return range.text;
1622            }
1623            return false;
1624        }
1625
1626        /**
1627         * Save selection
1628         */
1629        function doSave(editorID) {
1630            var $textarea = $('.richText-editor#' + editorID).siblings('.richText-initial');
1631            addHistory($textarea.val(), editorID);
1632            savedSelection = saveSelection(editorID);
1633        }
1634
1635        /**
1636         * @param editorID
1637         * @returns {boolean}
1638         */
1639        function updateMaxLength(editorID) {
1640            var $editorInner = $('.richText-editor#' + editorID);
1641            var $editor = $editorInner.parents('.richText');
1642            if (!$editor.data('maxlength')) {
1643                return true;
1644            }
1645            var color;
1646            var maxLength = parseInt($editor.data('maxlength'));
1647            var content = $editorInner.text();
1648            var percentage = (content.length / maxLength) * 100;
1649            if (percentage > 99) {
1650                color = 'red';
1651            } else if (percentage >= 90) {
1652                color = 'orange';
1653            } else {
1654                color = 'black';
1655            }
1656
1657            $editor.find('.richText-length').html('<span class="' + color + '">' + content.length + '</span>/' + maxLength);
1658
1659            if (content.length > maxLength) {
1660                // content too long
1661                undo($editor);
1662                return false;
1663            }
1664            return true;
1665        }
1666
1667        /**
1668         * Add to history
1669         * @param val Editor content
1670         * @param id Editor ID
1671         */
1672        function addHistory(val, id) {
1673            if (!history[id]) {
1674                return false;
1675            }
1676            if (history[id].length - 1 > historyPosition[id]) {
1677                history[id].length = historyPosition[id] + 1;
1678            }
1679
1680            if (history[id][history[id].length - 1] !== val) {
1681                history[id].push(val);
1682            }
1683
1684            historyPosition[id] = history[id].length - 1;
1685            setHistoryButtons(id);
1686        }
1687
1688        function setHistoryButtons(id) {
1689            if (historyPosition[id] <= 0) {
1690                $editor.find(".richText-undo").addClass("is-disabled");
1691            } else {
1692                $editor.find(".richText-undo").removeClass("is-disabled");
1693            }
1694
1695            if (historyPosition[id] >= history[id].length - 1 || history[id].length === 0) {
1696                $editor.find(".richText-redo").addClass("is-disabled");
1697            } else {
1698                $editor.find(".richText-redo").removeClass("is-disabled");
1699            }
1700        }
1701
1702        /**
1703         * Undo
1704         * @param $editor
1705         */
1706        function undo($editor) {
1707            var id = $editor.children('.richText-editor').attr('id');
1708            historyPosition[id]--;
1709            if (!historyPosition[id] && historyPosition[id] !== 0) {
1710                return false;
1711            }
1712            var value = history[id][historyPosition[id]];
1713            $editor.find('textarea').val(value);
1714            $editor.find('.richText-editor').html(value);
1715            setHistoryButtons(id);
1716        }
1717
1718        /**
1719         * Undo
1720         * @param $editor
1721         */
1722        function redo($editor) {
1723            var id = $editor.children('.richText-editor').attr('id');
1724            historyPosition[id]++;
1725            if (!historyPosition[id] && historyPosition[id] !== 0) {
1726                return false;
1727            }
1728            var value = history[id][historyPosition[id]];
1729            $editor.find('textarea').val(value);
1730            $editor.find('.richText-editor').html(value);
1731            setHistoryButtons(id);
1732        }
1733
1734        /**
1735         * Restore selection
1736         */
1737        function doRestore(id) {
1738            if (savedSelection) {
1739                restoreSelection((id ? id : savedSelection.editorID));
1740            }
1741        }
1742
1743        /**
1744         * Paste HTML at caret position
1745         * @param {string} html HTML code
1746         * @private
1747         */
1748        function pasteHTMLAtCaret(html) {
1749            // add HTML code for Internet Explorer
1750            var sel, range;
1751            if (window.getSelection) {
1752                // IE9 and non-IE
1753                sel = window.getSelection();
1754                if (sel.getRangeAt && sel.rangeCount) {
1755                    range = sel.getRangeAt(0);
1756                    range.deleteContents();
1757
1758                    // Range.createContextualFragment() would be useful here but is
1759                    // only relatively recently standardized and is not supported in
1760                    // some browsers (IE9, for one)
1761                    var el = document.createElement("div");
1762                    el.innerHTML = html;
1763                    var frag = document.createDocumentFragment(), node, lastNode;
1764                    while ((node = el.firstChild)) {
1765                        lastNode = frag.appendChild(node);
1766                    }
1767                    range.insertNode(frag);
1768
1769                    // Preserve the selection
1770                    if (lastNode) {
1771                        range = range.cloneRange();
1772                        range.setStartAfter(lastNode);
1773                        range.collapse(true);
1774                        sel.removeAllRanges();
1775                        sel.addRange(range);
1776                    }
1777                }
1778            } else if (document.selection && document.selection.type !== "Control") {
1779                // IE < 9
1780                document.selection.createRange().pasteHTML(html);
1781            }
1782        }
1783
1784
1785        /**
1786         * Change quotes around HTML attributes
1787         * @param  {string} string
1788         * @return {string}
1789         */
1790        function changeAttributeQuotes(string) {
1791            if (!string) {
1792                return '';
1793            }
1794
1795            var regex;
1796            var rstring;
1797            if (settings.useSingleQuotes === true) {
1798                regex = /\s+(\w+\s*=\s*(["][^"]*["])|(['][^']*[']))+/g;
1799                rstring = string.replace(regex, function ($0, $1, $2) {
1800                    if (!$2) {
1801                        return $0;
1802                    }
1803                    return $0.replace($2, $2.replace(/\"/g, "'"));
1804                });
1805            } else {
1806                regex = /\s+(\w+\s*=\s*(['][^']*['])|(["][^"]*["]))+/g;
1807                rstring = string.replace(regex, function ($0, $1, $2) {
1808                    if (!$2) {
1809                        return $0;
1810                    }
1811                    return $0.replace($2, $2.replace(/'/g, '"'));
1812                });
1813            }
1814            return rstring;
1815        }
1816
1817
1818        /**
1819         * Load colors for font or background
1820         * @param {string} command Command
1821         * @returns {string}
1822         * @private
1823         */
1824        function loadColors(command) {
1825            var colors = [];
1826            var result = '';
1827
1828            colors["#FFFFFF"] = settings.translations.white;
1829            colors["#000000"] = settings.translations.black;
1830            colors["#7F6000"] = settings.translations.brown;
1831            colors["#938953"] = settings.translations.beige;
1832            colors["#1F497D"] = settings.translations.darkBlue;
1833            colors["blue"] = settings.translations.blue;
1834            colors["#4F81BD"] = settings.translations.lightBlue;
1835            colors["#953734"] = settings.translations.darkRed;
1836            colors["red"] = settings.translations.red;
1837            colors["#4F6128"] = settings.translations.darkGreen;
1838            colors["green"] = settings.translations.green;
1839            colors["#3F3151"] = settings.translations.purple;
1840            colors["#31859B"] = settings.translations.darkTurquois;
1841            colors["#4BACC6"] = settings.translations.turquois;
1842            colors["#E36C09"] = settings.translations.darkOrange;
1843            colors["#F79646"] = settings.translations.orange;
1844            colors["#FFFF00"] = settings.translations.yellow;
1845
1846            if (settings.colors && settings.colors.length > 0) {
1847                colors = settings.colors;
1848            }
1849
1850            for (var i in colors) {
1851                result += '<li class="inline"><a data-command="' + command + '" data-option="' + i + '" style="text-align:left;" title="' + colors[i] + '"><span class="box-color" style="background-color:' + i + '"></span></a></li>';
1852            }
1853            return result;
1854        }
1855
1856
1857        /**
1858         * Toggle (show/hide) code or editor
1859         * @private
1860         */
1861        function toggleCode(editorID) {
1862            doRestore(editorID);
1863            if ($editor.find('.richText-editor').is(":visible")) {
1864                // show code
1865                $editor.find('.richText-initial').show();
1866                $editor.find('.richText-editor').hide();
1867                // disable non working buttons
1868                $('.richText-toolbar').find('.richText-btn').each(function () {
1869                    if ($(this).children('.fa-code').length === 0) {
1870                        $(this).parent('li').attr("data-disable", "true");
1871                    }
1872                });
1873                convertCaretPosition(editorID, savedSelection);
1874            } else {
1875                // show editor
1876                $editor.find('.richText-initial').hide();
1877                $editor.find('.richText-editor').show();
1878                convertCaretPosition(editorID, savedSelection, true);
1879                // enable all buttons again
1880                $('.richText-toolbar').find('li').removeAttr("data-disable");
1881            }
1882        }
1883
1884
1885        /**
1886         * Convert caret position from editor to code view (or in reverse)
1887         * @param {string} editorID
1888         * @param {object} selection
1889         * @param {boolean} reverse
1890         **/
1891        function convertCaretPosition(editorID, selection, reverse) {
1892            var $editor = $('#' + editorID);
1893            var $textarea = $editor.siblings(".richText-initial");
1894
1895            var code = $textarea.val();
1896            if (!selection || !code) {
1897                return {start: 0, end: 0};
1898            }
1899
1900            if (reverse === true) {
1901                savedSelection = {start: $editor.text().length, end: $editor.text().length, editorID: editorID};
1902                restoreSelection(editorID);
1903                return true;
1904            }
1905            selection.node = $textarea[0];
1906            var states = {
1907                start: false,
1908                end: false,
1909                tag: false,
1910                isTag: false,
1911                tagsCount: 0,
1912                isHighlight: (selection.start !== selection.end)
1913            };
1914            for (var i = 0; i < code.length; i++) {
1915                if (code[i] === "<") {
1916                    // HTML tag starts
1917                    states.isTag = true;
1918                    states.tag = false;
1919                    states.tagsCount++;
1920                } else if (states.isTag === true && code[i] !== ">") {
1921                    states.tagsCount++;
1922                } else if (states.isTag === true && code[i] === ">") {
1923                    states.isTag = false;
1924                    states.tag = true;
1925                    states.tagsCount++;
1926                } else if (states.tag === true) {
1927                    states.tag = false;
1928                }
1929
1930                if (!reverse) {
1931                    if ((selection.start + states.tagsCount) <= i && states.isHighlight && !states.isTag && !states.tag && !states.start) {
1932                        selection.start = i;
1933                        states.start = true;
1934                    } else if ((selection.start + states.tagsCount) <= i + 1 && !states.isHighlight && !states.isTag && !states.tag && !states.start) {
1935                        selection.start = i + 1;
1936                        states.start = true;
1937                    }
1938                    if ((selection.end + states.tagsCount) <= i + 1 && !states.isTag && !states.tag && !states.end) {
1939                        selection.end = i + 1;
1940                        states.end = true;
1941                    }
1942                }
1943
1944            }
1945            createSelection(selection.node, selection.start, selection.end);
1946            return selection;
1947        }
1948
1949        /**
1950         * Create selection on node element
1951         * @param {Node} field
1952         * @param {int} start
1953         * @param {int} end
1954         **/
1955        function createSelection(field, start, end) {
1956            if (field.createTextRange) {
1957                var selRange = field.createTextRange();
1958                selRange.collapse(true);
1959                selRange.moveStart('character', start);
1960                selRange.moveEnd('character', end);
1961                selRange.select();
1962                field.focus();
1963            } else if (field.setSelectionRange) {
1964                field.focus();
1965                field.setSelectionRange(start, end);
1966            } else if (typeof field.selectionStart != 'undefined') {
1967                field.selectionStart = start;
1968                field.selectionEnd = end;
1969                field.focus();
1970            }
1971        }
1972
1973
1974        /**
1975         * Get video embed code from URL
1976         * @param {string} url Video URL
1977         * @param {string} size Size in the form of widthxheight
1978         * @return {string|boolean}
1979         * @private
1980         **/
1981        function getVideoCode(url, size) {
1982            var video = getVideoID(url);
1983            var responsive = false, success = false;
1984
1985            if (!video) {
1986                // video URL not supported
1987                return false;
1988            }
1989
1990            if (!size) {
1991                size = "640x360";
1992                size = size.split("x");
1993            } else if (size !== "responsive") {
1994                size = size.split("x");
1995            } else {
1996                responsive = true;
1997                size = "640x360";
1998                size = size.split("x");
1999            }
2000
2001            var html = '<br><br>';
2002            if (responsive === true) {
2003                html += '<div class="videoEmbed" style="position:relative;height:0;padding-bottom:56.25%">';
2004            }
2005            var allowfullscreen = 'webkitallowfullscreen mozallowfullscreen allowfullscreen';
2006
2007            if (video.platform === "YouTube") {
2008                var youtubeDomain = (settings.youtubeCookies ? 'www.youtube.com' : 'www.youtube-nocookie.com');
2009                html += '<iframe src="https://' + youtubeDomain + '/embed/' + video.id + '?ecver=2" width="' + size[0] + '" height="' + size[1] + '" frameborder="0"' + (responsive === true ? ' style="position:absolute;width:100%;height:100%;left:0"' : '') + ' ' + allowfullscreen + '></iframe>';
2010                success = true;
2011            } else if (video.platform === "Vimeo") {
2012                html += '<iframe src="https://player.vimeo.com/video/' + video.id + '" width="' + size[0] + '" height="' + size[1] + '" frameborder="0"' + (responsive === true ? ' style="position:absolute;width:100%;height:100%;left:0"' : '') + ' ' + allowfullscreen + '></iframe>';
2013                success = true;
2014            } else if (video.platform === "Facebook") {
2015                html += '<iframe src="https://www.facebook.com/plugins/video.php?href=' + encodeURI(url) + '&show_text=0&width=' + size[0] + '" width="' + size[0] + '" height="' + size[1] + '" style="' + (responsive === true ? 'position:absolute;width:100%;height:100%;left:0;border:none;overflow:hidden"' : 'border:none;overflow:hidden') + '" scrolling="no" frameborder="0" allowTransparency="true" ' + allowfullscreen + '></iframe>';
2016                success = true;
2017            } else if (video.platform === "Dailymotion") {
2018                html += '<iframe frameborder="0" width="' + size[0] + '" height="' + size[1] + '" src="//www.dailymotion.com/embed/video/' + video.id + '"' + (responsive === true ? ' style="position:absolute;width:100%;height:100%;left:0"' : '') + ' ' + allowfullscreen + '></iframe>';
2019                success = true;
2020            }
2021
2022            if (responsive === true) {
2023                html += '</div>';
2024            }
2025            html += '<br><br>';
2026
2027            if (success) {
2028                return html;
2029            }
2030            return false;
2031        }
2032
2033        /**
2034         * Returns the unique video ID
2035         * @param {string} url
2036         * return {object|boolean}
2037         **/
2038        function getVideoID(url) {
2039            var vimeoRegExp = /(?:http?s?:\/\/)?(?:www\.)?(?:vimeo\.com)\/?(.+)/;
2040            var youtubeRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
2041            var facebookRegExp = /(?:http?s?:\/\/)?(?:www\.)?(?:facebook\.com)\/.*\/videos\/[0-9]+/;
2042            var dailymotionRegExp = /(?:http?s?:\/\/)?(?:www\.)?(?:dailymotion\.com)\/video\/([a-zA-Z0-9]+)/;
2043            var youtubeMatch = url.match(youtubeRegExp);
2044            var vimeoMatch = url.match(vimeoRegExp);
2045            var facebookMatch = url.match(facebookRegExp);
2046            var dailymotionMatch = url.match(dailymotionRegExp);
2047
2048            if (youtubeMatch && youtubeMatch[2].length === 11) {
2049                return {
2050                    "platform": "YouTube",
2051                    "id": youtubeMatch[2]
2052                };
2053            } else if (vimeoMatch && vimeoMatch[1]) {
2054                return {
2055                    "platform": "Vimeo",
2056                    "id": vimeoMatch[1]
2057                };
2058            } else if (facebookMatch && facebookMatch[0]) {
2059                return {
2060                    "platform": "Facebook",
2061                    "id": facebookMatch[0]
2062                };
2063            } else if (dailymotionMatch && dailymotionMatch[1]) {
2064                return {
2065                    "platform": "Dailymotion",
2066                    "id": dailymotionMatch[1]
2067                };
2068            }
2069
2070            return false;
2071        }
2072
2073
2074        /**
2075         * Fix the first line as by default the first line has no tag container
2076         */
2077        function fixFirstLine() {
2078            if ($editor && !$editor.find(".richText-editor").html()) {
2079                // set first line with the right tags
2080                if (settings.useParagraph !== false) {
2081                    $editor.find(".richText-editor").html('<p><br></p>');
2082                } else {
2083                    $editor.find(".richText-editor").html('<div><br></div>');
2084                }
2085            } else {
2086                // replace tags, to force <div> or <p> tags and fix issues
2087                if (settings.useParagraph !== false) {
2088                    $editor.find(".richText-editor").find('div:not(.videoEmbed)').replaceWith(function () {
2089                        return $('<p />', {html: $(this).html()});
2090                    });
2091                } else {
2092                    $editor.find(".richText-editor").find('p').replaceWith(function () {
2093                        return $('<div />', {html: $(this).html()});
2094                    });
2095                }
2096            }
2097            updateTextarea();
2098        }
2099
2100        return $(this);
2101    };
2102
2103    $.fn.unRichText = function (options) {
2104
2105        // set default options
2106        // and merge them with the parameter options
2107        var settings = $.extend({
2108            delay: 0 // delay in ms
2109        }, options);
2110
2111        var $editor, $textarea, $main;
2112        var $el = $(this);
2113
2114        /**
2115         * Initialize undoing RichText and call remove() method
2116         */
2117        function init() {
2118
2119            if ($el.hasClass('richText')) {
2120                $main = $el;
2121            } else if ($el.hasClass('richText-initial') || $el.hasClass('richText-editor')) {
2122                $main = $el.parents('.richText');
2123            }
2124
2125            if (!$main) {
2126                // node element does not correspond to RichText elements
2127                return false;
2128            }
2129
2130            $editor = $main.find('.richText-editor');
2131            $textarea = $main.find('.richText-initial');
2132
2133            if (parseInt(settings.delay) > 0) {
2134                // a delay has been set
2135                setTimeout(remove, parseInt(settings.delay));
2136            } else {
2137                remove();
2138            }
2139        }
2140
2141        init();
2142
2143        /**
2144         * Remove RichText elements
2145         */
2146        function remove() {
2147            $main.find('.richText-toolbar').remove();
2148            $main.find('.richText-editor').remove();
2149            $textarea
2150                .unwrap('.richText')
2151                .data('editor', 'richText')
2152                .removeClass('richText-initial')
2153                .show();
2154
2155            if (settings.callback && typeof settings.callback === 'function') {
2156                settings.callback();
2157            }
2158        }
2159
2160    };
2161
2162}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.