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.