jquery.richtext.js 83 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902
  1. (function($) {
  2. $.fn.richText = function(options) {
  3. // set default options
  4. // and merge them with the parameter options
  5. var settings = $.extend({
  6. // text formatting
  7. bold: true,
  8. italic: true,
  9. underline: true,
  10. // text alignment
  11. leftAlign: true,
  12. centerAlign: true,
  13. rightAlign: true,
  14. // lists
  15. ol: true,
  16. ul: true,
  17. // title
  18. heading: true,
  19. // fonts
  20. fonts: true,
  21. fontList: ["Arial",
  22. "Arial Black",
  23. "Comic Sans MS",
  24. "Courier New",
  25. "Geneva",
  26. "Georgia",
  27. "Helvetica",
  28. "Impact",
  29. "Lucida Console",
  30. "Tahoma",
  31. "Times New Roman",
  32. "Verdana"
  33. ],
  34. fontColor: true,
  35. fontSize: true,
  36. // uploads
  37. imageUpload: true,
  38. fileUpload: true,
  39. // media
  40. videoEmbed: true,
  41. // link
  42. urls: true,
  43. // tables
  44. table: true,
  45. // code
  46. removeStyles: true,
  47. code: true,
  48. // colors
  49. colors: [],
  50. // dropdowns
  51. fileHTML: '',
  52. imageHTML: '',
  53. // translations
  54. translations: {
  55. 'title': 'Title',
  56. 'white': 'White',
  57. 'black': 'Black',
  58. 'brown': 'Brown',
  59. 'beige': 'Beige',
  60. 'darkBlue': 'Dark Blue',
  61. 'blue': 'Blue',
  62. 'lightBlue': 'Light Blue',
  63. 'darkRed': 'Dark Red',
  64. 'red': 'Red',
  65. 'darkGreen': 'Dark Green',
  66. 'green': 'Green',
  67. 'purple': 'Purple',
  68. 'darkTurquois': 'Dark Turquois',
  69. 'turquois': 'Turquois',
  70. 'darkOrange': 'Dark Orange',
  71. 'orange': 'Orange',
  72. 'yellow': 'Yellow',
  73. 'imageURL': 'Image URL',
  74. 'fileURL': 'File URL',
  75. 'linkText': 'Link text',
  76. 'url': 'URL',
  77. 'size': 'Size',
  78. 'responsive': 'Responsive',
  79. 'text': 'Text',
  80. 'openIn': 'Open in',
  81. 'sameTab': 'Same tab',
  82. 'newTab': 'New tab',
  83. 'align': 'Align',
  84. 'left': 'Left',
  85. 'center': 'Center',
  86. 'right': 'Right',
  87. 'rows': 'Rows',
  88. 'columns': 'Columns',
  89. 'add': 'Add',
  90. 'pleaseEnterURL': 'Please enter an URL',
  91. 'videoURLnotSupported': 'Video URL not supported',
  92. 'pleaseSelectImage': 'Please select an image',
  93. 'pleaseSelectFile': 'Please select a file',
  94. 'bold': 'Bold',
  95. 'italic': 'Italic',
  96. 'underline': 'Underline',
  97. 'alignLeft': 'Align left',
  98. 'alignCenter': 'Align centered',
  99. 'alignRight': 'Align right',
  100. 'addOrderedList': 'Add ordered list',
  101. 'addUnorderedList': 'Add unordered list',
  102. 'addHeading': 'Add Heading/title',
  103. 'addFont': 'Add font',
  104. 'addFontColor': 'Add font color',
  105. 'addFontSize': 'Add font size',
  106. 'addImage': 'Add image',
  107. 'addVideo': 'Add video',
  108. 'addFile': 'Add file',
  109. 'addURL': 'Add URL',
  110. 'addTable': 'Add table',
  111. 'removeStyles': 'Remove styles',
  112. 'code': 'Show HTML code',
  113. 'undo': 'Undo',
  114. 'redo': 'Redo',
  115. 'close': 'Close'
  116. },
  117. // privacy
  118. youtubeCookies: false,
  119. // dev settings
  120. useSingleQuotes: false,
  121. height: 0,
  122. heightPercentage: 0,
  123. id: "",
  124. class: "",
  125. useParagraph: false
  126. }, options);
  127. /* prepare toolbar */
  128. var $inputElement = $(this);
  129. $inputElement.addClass("richText-initial");
  130. var $editor,
  131. $toolbarList = $('<ul />'),
  132. $toolbarElement = $('<li />'),
  133. $btnBold = $('<a />', { class: "richText-btn", "data-command": "bold", "title": settings.translations.bold, html: '<span class="fa fa-bold"></span>' }), // bold
  134. $btnItalic = $('<a />', { class: "richText-btn", "data-command": "italic", "title": settings.translations.italic, html: '<span class="fa fa-italic"></span>' }), // italic
  135. $btnUnderline = $('<a />', { class: "richText-btn", "data-command": "underline", "title": settings.translations.underline, html: '<span class="fa fa-underline"></span>' }), // underline
  136. $btnLeftAlign = $('<a />', { class: "richText-btn", "data-command": "justifyLeft", "title": settings.translations.alignLeft, html: '<span class="fa fa-align-left"></span>' }), // left align
  137. $btnCenterAlign = $('<a />', { class: "richText-btn", "data-command": "justifyCenter", "title": settings.translations.alignCenter, html: '<span class="fa fa-align-center"></span>' }), // centered
  138. $btnRightAlign = $('<a />', { class: "richText-btn", "data-command": "justifyRight", "title": settings.translations.alignRight, html: '<span class="fa fa-align-right"></span>' }), // right align
  139. $btnOL = $('<a />', { class: "richText-btn", "data-command": "insertOrderedList", "title": settings.translations.addOrderedList, html: '<span class="fa fa-list-ol"></span>' }), // ordered list
  140. $btnUL = $('<a />', { class: "richText-btn", "data-command": "insertUnorderedList", "title": settings.translations.addUnorderedList, html: '<span class="fa fa-list"></span>' }), // unordered list
  141. $btnHeading = $('<a />', { class: "richText-btn", "title": settings.translations.addHeading, html: '<span class="fa fa-header fa-heading"></span>' }), // title/header
  142. $btnFont = $('<a />', { class: "richText-btn", "title": settings.translations.addFont, html: '<span class="fa fa-font"></span>' }), // font color
  143. $btnFontColor = $('<a />', { class: "richText-btn", "title": settings.translations.addFontColor, html: '<span class="fa fa-paint-brush"></span>' }), // font color
  144. $btnFontSize = $('<a />', { class: "richText-btn", "title": settings.translations.addFontSize, html: '<span class="fa fa-text-height"></span>' }), // font color
  145. $btnImageUpload = $('<a />', { class: "richText-btn", "title": settings.translations.addImage, html: '<span class="fa fa-image"></span>' }), // image
  146. $btnVideoEmbed = $('<a />', { class: "richText-btn", "title": settings.translations.addVideo, html: '<span class="fa fa-video-camera fa-video"></span>' }), // video
  147. $btnFileUpload = $('<a />', { class: "richText-btn", "title": settings.translations.addFile, html: '<span class="fa fa-file-text-o far fa-file-alt"></span>' }), // file
  148. $btnURLs = $('<a />', { class: "richText-btn", "title": settings.translations.addURL, html: '<span class="fa fa-link"></span>' }), // urls/links
  149. $btnTable = $('<a />', { class: "richText-btn", "title": settings.translations.addTable, html: '<span class="fa fa-table"></span>' }), // table
  150. $btnRemoveStyles = $('<a />', { class: "richText-btn", "data-command": "removeFormat", "title": settings.translations.removeStyles, html: '<span class="fa fa-recycle"></span>' }), // clean up styles
  151. $btnCode = $('<a />', { class: "richText-btn", "data-command": "toggleCode", "title": settings.translations.code, html: '<span class="fa fa-code"></span>' }); // code
  152. /* prepare toolbar dropdowns */
  153. var $dropdownOuter = $('<div />', { class: "richText-dropdown-outer" });
  154. var $dropdownClose = $('<span />', { class: "richText-dropdown-close", html: '<span title="' + settings.translations.close + '"><span class="fe fe-x"></span></span>' });
  155. var $dropdownList = $('<ul />', { class: "richText-dropdown" }), // dropdown lists
  156. $dropdownBox = $('<div />', { class: "richText-dropdown" }), // dropdown boxes / custom dropdowns
  157. $form = $('<div />', { class: "richText-form" }), // symbolic form
  158. $formItem = $('<div />', { class: 'richText-form-item' }), // form item
  159. $formLabel = $('<label />'), // form label
  160. $formInput = $('<input />', { type: "text" }), //form input field
  161. $formInputFile = $('<input />', { type: "file" }), // form file input field
  162. $formInputSelect = $('<select />'),
  163. $formButton = $('<button />', { text: settings.translations.add, class: "btn" }); // button
  164. /* internal settings */
  165. var savedSelection; // caret position/selection
  166. var editorID = "richText-" + Math.random().toString(36).substring(7);
  167. var ignoreSave = false,
  168. $resizeImage = null,
  169. history = [],
  170. historyPosition = 0;
  171. /* list dropdown for titles */
  172. var $titles = $dropdownList.clone();
  173. $titles.append($('<li />', { html: '<a data-command="formatBlock" data-option="h1">' + settings.translations.title + ' #1</a>' }));
  174. $titles.append($('<li />', { html: '<a data-command="formatBlock" data-option="h2">' + settings.translations.title + ' #2</a>' }));
  175. $titles.append($('<li />', { html: '<a data-command="formatBlock" data-option="h3">' + settings.translations.title + ' #3</a>' }));
  176. $titles.append($('<li />', { html: '<a data-command="formatBlock" data-option="h4">' + settings.translations.title + ' #4</a>' }));
  177. $btnHeading.append($dropdownOuter.clone().append($titles.prepend($dropdownClose.clone())));
  178. /* list dropdown for fonts */
  179. var fonts = settings.fontList;
  180. var $fonts = $dropdownList.clone();
  181. for (var i = 0; i < fonts.length; i++) {
  182. $fonts.append($('<li />', { html: '<a style="font-family:' + fonts[i] + ';" data-command="fontName" data-option="' + fonts[i] + '">' + fonts[i] + '</a>' }));
  183. }
  184. $btnFont.append($dropdownOuter.clone().append($fonts.prepend($dropdownClose.clone())));
  185. /* list dropdown for font sizes */
  186. var fontSizes = [24, 18, 16, 14, 12];
  187. var $fontSizes = $dropdownList.clone();
  188. for (var i = 0; i < fontSizes.length; i++) {
  189. $fontSizes.append($('<li />', { html: '<a style="font-size:' + fontSizes[i] + 'px;" data-command="fontSize" data-option="' + fontSizes[i] + '">Text ' + fontSizes[i] + 'px</a>' }));
  190. }
  191. $btnFontSize.append($dropdownOuter.clone().append($fontSizes.prepend($dropdownClose.clone())));
  192. /* font colors */
  193. var $fontColors = $dropdownList.clone();
  194. $fontColors.html(loadColors("forecolor"));
  195. $btnFontColor.append($dropdownOuter.clone().append($fontColors.prepend($dropdownClose.clone())));
  196. /* background colors */
  197. //var $bgColors = $dropdownList.clone();
  198. //$bgColors.html(loadColors("hiliteColor"));
  199. //$btnBGColor.append($dropdownOuter.clone().append($bgColors));
  200. /* box dropdown for links */
  201. var $linksDropdown = $dropdownBox.clone();
  202. var $linksForm = $form.clone().attr("id", "richText-URL").attr("data-editor", editorID);
  203. $linksForm.append(
  204. $formItem.clone()
  205. .append($formLabel.clone().text(settings.translations.url).attr("for", "url"))
  206. .append($formInput.clone().attr("id", "url"))
  207. );
  208. $linksForm.append(
  209. $formItem.clone()
  210. .append($formLabel.clone().text(settings.translations.text).attr("for", "urlText"))
  211. .append($formInput.clone().attr("id", "urlText"))
  212. );
  213. $linksForm.append(
  214. $formItem.clone()
  215. .append($formLabel.clone().text(settings.translations.openIn).attr("for", "openIn"))
  216. .append(
  217. $formInputSelect
  218. .clone().attr("id", "openIn")
  219. .append($("<option />", { value: '_self', text: settings.translations.sameTab }))
  220. .append($("<option />", { value: '_blank', text: settings.translations.newTab }))
  221. )
  222. );
  223. $linksForm.append($formItem.clone().append($formButton.clone()));
  224. $linksDropdown.append($linksForm);
  225. $btnURLs.append($dropdownOuter.clone().append($linksDropdown.prepend($dropdownClose.clone())));
  226. /* box dropdown for video embedding */
  227. var $videoDropdown = $dropdownBox.clone();
  228. var $videoForm = $form.clone().attr("id", "richText-Video").attr("data-editor", editorID);
  229. $videoForm.append(
  230. $formItem.clone()
  231. .append($formLabel.clone().text(settings.translations.url).attr("for", "videoURL"))
  232. .append($formInput.clone().attr("id", "videoURL"))
  233. );
  234. $videoForm.append(
  235. $formItem.clone()
  236. .append($formLabel.clone().text(settings.translations.size).attr("for", "size"))
  237. .append(
  238. $formInputSelect
  239. .clone().attr("id", "size")
  240. .append($("<option />", { value: 'responsive', text: settings.translations.responsive }))
  241. .append($("<option />", { value: '640x360', text: '640x360' }))
  242. .append($("<option />", { value: '560x315', text: '560x315' }))
  243. .append($("<option />", { value: '480x270', text: '480x270' }))
  244. .append($("<option />", { value: '320x180', text: '320x180' }))
  245. )
  246. );
  247. $videoForm.append($formItem.clone().append($formButton.clone()));
  248. $videoDropdown.append($videoForm);
  249. $btnVideoEmbed.append($dropdownOuter.clone().append($videoDropdown.prepend($dropdownClose.clone())));
  250. /* box dropdown for image upload/image selection */
  251. var $imageDropdown = $dropdownBox.clone();
  252. var $imageForm = $form.clone().attr("id", "richText-Image").attr("data-editor", editorID);
  253. if (settings.imageHTML &&
  254. ($(settings.imageHTML).find('#imageURL').length > 0 || $(settings.imageHTML).attr("id") === "imageURL")) {
  255. // custom image form
  256. $imageForm.html(settings.imageHTML);
  257. } else {
  258. // default image form
  259. $imageForm.append(
  260. $formItem.clone()
  261. .append($formLabel.clone().text(settings.translations.imageURL).attr("for", "imageURL"))
  262. .append($formInput.clone().attr("id", "imageURL"))
  263. );
  264. $imageForm.append(
  265. $formItem.clone()
  266. .append($formLabel.clone().text(settings.translations.align).attr("for", "align"))
  267. .append(
  268. $formInputSelect
  269. .clone().attr("id", "align")
  270. .append($("<option />", { value: 'left', text: settings.translations.left }))
  271. .append($("<option />", { value: 'center', text: settings.translations.center }))
  272. .append($("<option />", { value: 'right', text: settings.translations.right }))
  273. )
  274. );
  275. }
  276. $imageForm.append($formItem.clone().append($formButton.clone()));
  277. $imageDropdown.append($imageForm);
  278. $btnImageUpload.append($dropdownOuter.clone().append($imageDropdown.prepend($dropdownClose.clone())));
  279. /* box dropdown for file upload/file selection */
  280. var $fileDropdown = $dropdownBox.clone();
  281. var $fileForm = $form.clone().attr("id", "richText-File").attr("data-editor", editorID);
  282. if (settings.fileHTML &&
  283. ($(settings.fileHTML).find('#fileURL').length > 0 || $(settings.fileHTML).attr("id") === "fileURL")) {
  284. // custom file form
  285. $fileForm.html(settings.fileHTML);
  286. } else {
  287. // default file form
  288. $fileForm.append(
  289. $formItem.clone()
  290. .append($formLabel.clone().text(settings.translations.fileURL).attr("for", "fileURL"))
  291. .append($formInput.clone().attr("id", "fileURL"))
  292. );
  293. $fileForm.append(
  294. $formItem.clone()
  295. .append($formLabel.clone().text(settings.translations.linkText).attr("for", "fileText"))
  296. .append($formInput.clone().attr("id", "fileText"))
  297. );
  298. }
  299. $fileForm.append($formItem.clone().append($formButton.clone()));
  300. $fileDropdown.append($fileForm);
  301. $btnFileUpload.append($dropdownOuter.clone().append($fileDropdown.prepend($dropdownClose.clone())));
  302. /* box dropdown for tables */
  303. var $tableDropdown = $dropdownBox.clone();
  304. var $tableForm = $form.clone().attr("id", "richText-Table").attr("data-editor", editorID);
  305. $tableForm.append(
  306. $formItem.clone()
  307. .append($formLabel.clone().text(settings.translations.rows).attr("for", "tableRows"))
  308. .append($formInput.clone().attr("id", "tableRows").attr("type", "number"))
  309. );
  310. $tableForm.append(
  311. $formItem.clone()
  312. .append($formLabel.clone().text(settings.translations.columns).attr("for", "tableColumns"))
  313. .append($formInput.clone().attr("id", "tableColumns").attr("type", "number"))
  314. );
  315. $tableForm.append($formItem.clone().append($formButton.clone()));
  316. $tableDropdown.append($tableForm);
  317. $btnTable.append($dropdownOuter.clone().append($tableDropdown.prepend($dropdownClose.clone())));
  318. /* initizalize editor */
  319. function init() {
  320. var value, attributes, attributes_html = '';
  321. if (settings.useParagraph !== false) {
  322. // set default tag when pressing ENTER to <p> instead of <div>
  323. document.execCommand("DefaultParagraphSeparator", false, 'p');
  324. }
  325. // reformat $inputElement to textarea
  326. if ($inputElement.prop("tagName") === "TEXTAREA") {
  327. // everything perfect
  328. } else if ($inputElement.val()) {
  329. value = $inputElement.val();
  330. attributes = $inputElement.prop("attributes");
  331. // loop through <select> attributes and apply them on <div>
  332. $.each(attributes, function() {
  333. if (this.name) {
  334. attributes_html += ' ' + this.name + '="' + this.value + '"';
  335. }
  336. });
  337. $inputElement.replaceWith($('<textarea' + attributes_html + ' data-richtext="init">' + value + '</textarea>'));
  338. $inputElement = $('[data-richtext="init"]');
  339. $inputElement.removeAttr("data-richtext");
  340. } else if ($inputElement.html()) {
  341. value = $inputElement.html();
  342. attributes = $inputElement.prop("attributes");
  343. // loop through <select> attributes and apply them on <div>
  344. $.each(attributes, function() {
  345. if (this.name) {
  346. attributes_html += ' ' + this.name + '="' + this.value + '"';
  347. }
  348. });
  349. $inputElement.replaceWith($('<textarea' + attributes_html + ' data-richtext="init">' + value + '</textarea>'));
  350. $inputElement = $('[data-richtext="init"]');
  351. $inputElement.removeAttr("data-richtext");
  352. } else {
  353. attributes = $inputElement.prop("attributes");
  354. // loop through <select> attributes and apply them on <div>
  355. $.each(attributes, function() {
  356. if (this.name) {
  357. attributes_html += ' ' + this.name + '="' + this.value + '"';
  358. }
  359. });
  360. $inputElement.replaceWith($('<textarea' + attributes_html + ' data-richtext="init"></textarea>'));
  361. $inputElement = $('[data-richtext="init"]');
  362. $inputElement.removeAttr("data-richtext");
  363. }
  364. $editor = $('<div />', { class: "richText" });
  365. var $toolbar = $('<div />', { class: "richText-toolbar" });
  366. var $editorView = $('<div />', { class: "richText-editor", id: editorID, contenteditable: true });
  367. $toolbar.append($toolbarList);
  368. /* text formatting */
  369. if (settings.bold === true) {
  370. $toolbarList.append($toolbarElement.clone().append($btnBold));
  371. }
  372. if (settings.italic === true) {
  373. $toolbarList.append($toolbarElement.clone().append($btnItalic));
  374. }
  375. if (settings.underline === true) {
  376. $toolbarList.append($toolbarElement.clone().append($btnUnderline));
  377. }
  378. /* align */
  379. if (settings.leftAlign === true) {
  380. $toolbarList.append($toolbarElement.clone().append($btnLeftAlign));
  381. }
  382. if (settings.centerAlign === true) {
  383. $toolbarList.append($toolbarElement.clone().append($btnCenterAlign));
  384. }
  385. if (settings.rightAlign === true) {
  386. $toolbarList.append($toolbarElement.clone().append($btnRightAlign));
  387. }
  388. /* lists */
  389. if (settings.ol === true) {
  390. $toolbarList.append($toolbarElement.clone().append($btnOL));
  391. }
  392. if (settings.ul === true) {
  393. $toolbarList.append($toolbarElement.clone().append($btnUL));
  394. }
  395. /* fonts */
  396. if (settings.fonts === true && settings.fontList.length > 0) {
  397. $toolbarList.append($toolbarElement.clone().append($btnFont));
  398. }
  399. if (settings.fontSize === true) {
  400. $toolbarList.append($toolbarElement.clone().append($btnFontSize));
  401. }
  402. /* heading */
  403. if (settings.heading === true) {
  404. $toolbarList.append($toolbarElement.clone().append($btnHeading));
  405. }
  406. /* colors */
  407. if (settings.fontColor === true) {
  408. $toolbarList.append($toolbarElement.clone().append($btnFontColor));
  409. }
  410. /* uploads */
  411. if (settings.imageUpload === true) {
  412. $toolbarList.append($toolbarElement.clone().append($btnImageUpload));
  413. }
  414. if (settings.fileUpload === true) {
  415. $toolbarList.append($toolbarElement.clone().append($btnFileUpload));
  416. }
  417. /* media */
  418. if (settings.videoEmbed === true) {
  419. $toolbarList.append($toolbarElement.clone().append($btnVideoEmbed));
  420. }
  421. /* urls */
  422. if (settings.urls === true) {
  423. $toolbarList.append($toolbarElement.clone().append($btnURLs));
  424. }
  425. if (settings.table === true) {
  426. $toolbarList.append($toolbarElement.clone().append($btnTable));
  427. }
  428. /* code */
  429. if (settings.removeStyles === true) {
  430. $toolbarList.append($toolbarElement.clone().append($btnRemoveStyles));
  431. }
  432. if (settings.code === true) {
  433. $toolbarList.append($toolbarElement.clone().append($btnCode));
  434. }
  435. // set current textarea value to editor
  436. $editorView.html($inputElement.val());
  437. $editor.append($toolbar);
  438. $editor.append($editorView);
  439. $editor.append($inputElement.clone().hide());
  440. $inputElement.replaceWith($editor);
  441. // append bottom toolbar
  442. $editor.append(
  443. $('<div />', { class: 'richText-toolbar' })
  444. .append($('<a />', { class: 'richText-undo is-disabled', html: '<span class="fa fa-undo"></span>', 'title': settings.translations.undo }))
  445. .append($('<a />', { class: 'richText-redo is-disabled', html: '<span class="fa fa-repeat fa-redo"></span>', 'title': settings.translations.redo }))
  446. .append($('<a />', { class: 'richText-help', html: '' }))
  447. );
  448. if (settings.height && settings.height > 0) {
  449. // set custom editor height
  450. $editor.children(".richText-editor, .richText-initial").css({ 'min-height': settings.height + 'px', 'height': settings.height + 'px' });
  451. } else if (settings.heightPercentage && settings.heightPercentage > 0) {
  452. // set custom editor height in percentage
  453. var parentHeight = $editor.parent().innerHeight(); // get editor parent height
  454. var height = (settings.heightPercentage / 100) * parentHeight; // calculate pixel value from percentage
  455. height -= $toolbar.outerHeight() * 2; // remove toolbar size
  456. height -= parseInt($editor.css("margin-top")); // remove margins
  457. height -= parseInt($editor.css("margin-bottom")); // remove margins
  458. height -= parseInt($editor.find(".richText-editor").css("padding-top")); // remove paddings
  459. height -= parseInt($editor.find(".richText-editor").css("padding-bottom")); // remove paddings
  460. $editor.children(".richText-editor, .richText-initial").css({ 'min-height': height + 'px', 'height': height + 'px' });
  461. }
  462. // add custom class
  463. if (settings.class) {
  464. $editor.addClass(settings.class);
  465. }
  466. if (settings.id) {
  467. $editor.attr("id", settings.id);
  468. }
  469. // fix the first line
  470. fixFirstLine();
  471. // save history
  472. history.push($editor.find("textarea").val());
  473. }
  474. // initialize editor
  475. init();
  476. /** EVENT HANDLERS */
  477. // undo / redo
  478. $(document).on("click", ".richText-undo, .richText-redo", function(e) {
  479. var $this = $(this);
  480. if ($this.hasClass("richText-undo") && !$this.hasClass("is-disabled")) {
  481. undo();
  482. } else if ($this.hasClass("richText-redo") && !$this.hasClass("is-disabled")) {
  483. redo();
  484. }
  485. });
  486. // Saving changes from editor to textarea
  487. $(document).on("input change blur keydown keyup", ".richText-editor", function(e) {
  488. if ((e.keyCode === 9 || e.keyCode === "9") && e.type === "keydown") {
  489. // tab through table cells
  490. e.preventDefault();
  491. tabifyEditableTable(window, e);
  492. return false;
  493. }
  494. fixFirstLine();
  495. updateTextarea();
  496. doSave($(this).attr("id"));
  497. });
  498. // add context menu to several Node elements
  499. $(document).on('contextmenu', '.richText-editor', function(e) {
  500. var $list = $('<ul />', { 'class': 'list-rightclick richText-list' });
  501. var $li = $('<li />');
  502. // remove Node selection
  503. $('.richText-editor').find('.richText-editNode').removeClass('richText-editNode');
  504. var $target = $(e.target);
  505. var $richText = $target.parents('.richText');
  506. var $toolbar = $richText.find('.richText-toolbar');
  507. var positionX = e.pageX - $richText.offset().left;
  508. var positionY = e.pageY - $richText.offset().top;
  509. $list.css({
  510. 'top': positionY,
  511. 'left': positionX
  512. });
  513. if ($target.prop("tagName") === "A") {
  514. // edit URL
  515. e.preventDefault();
  516. $list.append($li.clone().html('<span class="fa fa-link"></span>'));
  517. $target.parents('.richText').append($list);
  518. $list.find('.fa-link').on('click', function() {
  519. $('.list-rightclick.richText-list').remove();
  520. $target.addClass('richText-editNode');
  521. var $popup = $toolbar.find('#richText-URL');
  522. $popup.find('input#url').val($target.attr('href'));
  523. $popup.find('input#urlText').val($target.text());
  524. $popup.find('select#openIn').val($target.attr('target'));
  525. $toolbar.find('.richText-btn').children('.fa-link').parents('li').addClass('is-selected');
  526. });
  527. return false;
  528. } else if ($target.prop("tagName") === "IMG") {
  529. // edit image
  530. e.preventDefault();
  531. $list.append($li.clone().html('<span class="fa fa-image"></span>'));
  532. $target.parents('.richText').append($list);
  533. $list.find('.fa-image').on('click', function() {
  534. var align;
  535. if ($target.parent('div').length > 0 && $target.parent('div').attr('style') === 'text-align:center;') {
  536. align = 'center';
  537. } else {
  538. align = $target.attr('align');
  539. }
  540. $('.list-rightclick.richText-list').remove();
  541. $target.addClass('richText-editNode');
  542. var $popup = $toolbar.find('#richText-Image');
  543. $popup.find('input#imageURL').val($target.attr('src'));
  544. $popup.find('select#align').val(align);
  545. $toolbar.find('.richText-btn').children('.fa-image').parents('li').addClass('is-selected');
  546. });
  547. return false;
  548. }
  549. });
  550. // Saving changes from textarea to editor
  551. $(document).on("input change blur", ".richText-initial", function() {
  552. if (settings.useSingleQuotes === true) {
  553. $(this).val(changeAttributeQuotes($(this).val()));
  554. }
  555. var editorID = $(this).siblings('.richText-editor').attr("id");
  556. updateEditor(editorID);
  557. doSave(editorID);
  558. });
  559. // Save selection seperately (mainly needed for Safari)
  560. $(document).on("dblclick mouseup", ".richText-editor", function() {
  561. var editorID = $(this).attr("id");
  562. doSave(editorID);
  563. });
  564. // embedding video
  565. $(document).on("click", "#richText-Video button.btn", function(event) {
  566. event.preventDefault();
  567. var $button = $(this);
  568. var $form = $button.parent('.richText-form-item').parent('.richText-form');
  569. if ($form.attr("data-editor") === editorID) {
  570. // only for the currently selected editor
  571. var url = $form.find('input#videoURL').val();
  572. var size = $form.find('select#size').val();
  573. if (!url) {
  574. // no url set
  575. $form.prepend($('<div />', { style: 'color:red;display:none;', class: 'form-item is-error', text: settings.translations.pleaseEnterURL }));
  576. $form.children('.form-item.is-error').slideDown();
  577. setTimeout(function() {
  578. $form.children('.form-item.is-error').slideUp(function() {
  579. $(this).remove();
  580. });
  581. }, 5000);
  582. } else {
  583. // write html in editor
  584. var html = '';
  585. html = getVideoCode(url, size);
  586. if (!html) {
  587. $form.prepend($('<div />', { style: 'color:red;display:none;', class: 'form-item is-error', text: settings.translations.videoURLnotSupported }));
  588. $form.children('.form-item.is-error').slideDown();
  589. setTimeout(function() {
  590. $form.children('.form-item.is-error').slideUp(function() {
  591. $(this).remove();
  592. });
  593. }, 5000);
  594. } else {
  595. if (settings.useSingleQuotes === true) {
  596. } else {
  597. }
  598. restoreSelection(editorID, true);
  599. pasteHTMLAtCaret(html);
  600. updateTextarea();
  601. // reset input values
  602. $form.find('input#videoURL').val('');
  603. $('.richText-toolbar li.is-selected').removeClass("is-selected");
  604. }
  605. }
  606. }
  607. });
  608. // Resize images
  609. $(document).on('mousedown', function(e) {
  610. var $target = $(e.target);
  611. if (!$target.hasClass('richText-list') && $target.parents('.richText-list').length === 0) {
  612. // remove context menu
  613. $('.richText-list.list-rightclick').remove();
  614. if (!$target.hasClass('richText-form') && $target.parents('.richText-form').length === 0) {
  615. $('.richText-editNode').each(function() {
  616. var $this = $(this);
  617. $this.removeClass('richText-editNode');
  618. if ($this.attr('class') === '') {
  619. $this.removeAttr('class');
  620. }
  621. });
  622. }
  623. }
  624. if ($target.prop("tagName") === "IMG" && $target.parents("#" + editorID)) {
  625. startX = e.pageX;
  626. startY = e.pageY;
  627. startW = $target.innerWidth();
  628. startH = $target.innerHeight();
  629. var left = $target.offset().left;
  630. var right = $target.offset().left + $target.innerWidth();
  631. var bottom = $target.offset().top + $target.innerHeight();
  632. var top = $target.offset().top;
  633. var resize = false;
  634. $target.css({ 'cursor': 'default' });
  635. if (startY <= bottom && startY >= bottom - 20 && startX >= right - 20 && startX <= right) {
  636. // bottom right corner
  637. $resizeImage = $target;
  638. $resizeImage.css({ 'cursor': 'nwse-resize' });
  639. resize = true;
  640. }
  641. if ((resize === true || $resizeImage) && !$resizeImage.data("width")) {
  642. // set initial image size and prevent dragging image while resizing
  643. $resizeImage.data("width", $target.parents("#" + editorID).innerWidth());
  644. $resizeImage.data("height", $target.parents("#" + editorID).innerHeight() * 3);
  645. e.preventDefault();
  646. } else if (resize === true || $resizeImage) {
  647. // resizing active, prevent other events
  648. e.preventDefault();
  649. } else {
  650. // resizing disabled, allow dragging image
  651. $resizeImage = null;
  652. }
  653. }
  654. });
  655. $(document)
  656. .mouseup(function() {
  657. if ($resizeImage) {
  658. $resizeImage.css({ 'cursor': 'default' });
  659. }
  660. $resizeImage = null;
  661. })
  662. .mousemove(function(e) {
  663. if ($resizeImage !== null) {
  664. var maxWidth = $resizeImage.data('width');
  665. var currentWidth = $resizeImage.width();
  666. var maxHeight = $resizeImage.data('height');
  667. var currentHeight = $resizeImage.height();
  668. if ((startW + e.pageX - startX) <= maxWidth && (startH + e.pageY - startY) <= maxHeight) {
  669. // only resize if new size is smaller than the original image size
  670. $resizeImage.innerWidth(startW + e.pageX - startX); // only resize width to adapt height proportionally
  671. // $box.innerHeight(startH + e.pageY-startY);
  672. updateTextarea();
  673. } else if ((startW + e.pageX - startX) <= currentWidth && (startH + e.pageY - startY) <= currentHeight) {
  674. // only resize if new size is smaller than the previous size
  675. $resizeImage.innerWidth(startW + e.pageX - startX); // only resize width to adapt height proportionally
  676. updateTextarea();
  677. }
  678. }
  679. });
  680. // adding URL
  681. $(document).on("click", "#richText-URL button.btn", function(event) {
  682. event.preventDefault();
  683. var $button = $(this);
  684. var $form = $button.parent('.richText-form-item').parent('.richText-form');
  685. if ($form.attr("data-editor") === editorID) {
  686. // only for currently selected editor
  687. var url = $form.find('input#url').val();
  688. var text = $form.find('input#urlText').val();
  689. var target = $form.find('#openIn').val();
  690. // set default values
  691. if (!target) {
  692. target = '_self';
  693. }
  694. if (!text) {
  695. text = url;
  696. }
  697. if (!url) {
  698. // no url set
  699. $form.prepend($('<div />', { style: 'color:red;display:none;', class: 'form-item is-error', text: settings.translations.pleaseEnterURL }));
  700. $form.children('.form-item.is-error').slideDown();
  701. setTimeout(function() {
  702. $form.children('.form-item.is-error').slideUp(function() {
  703. $(this).remove();
  704. });
  705. }, 5000);
  706. } else {
  707. // write html in editor
  708. var html = '';
  709. if (settings.useSingleQuotes === true) {
  710. html = "<a href='" + url + "' target='" + target + "'>" + text + "</a>";
  711. } else {
  712. html = '<a href="' + url + '" target="' + target + '">' + text + '</a>';
  713. }
  714. restoreSelection(editorID, false, true);
  715. var $editNode = $('.richText-editNode');
  716. if ($editNode.length > 0 && $editNode.prop("tagName") === "A") {
  717. $editNode.attr("href", url);
  718. $editNode.attr("target", target);
  719. $editNode.text(text);
  720. $editNode.removeClass('richText-editNode');
  721. if ($editNode.attr('class') === '') {
  722. $editNode.removeAttr('class');
  723. }
  724. } else {
  725. pasteHTMLAtCaret(html);
  726. }
  727. // reset input values
  728. $form.find('input#url').val('');
  729. $form.find('input#urlText').val('');
  730. $('.richText-toolbar li.is-selected').removeClass("is-selected");
  731. }
  732. }
  733. });
  734. // adding image
  735. $(document).on("click", "#richText-Image button.btn", function(event) {
  736. event.preventDefault();
  737. var $button = $(this);
  738. var $form = $button.parent('.richText-form-item').parent('.richText-form');
  739. if ($form.attr("data-editor") === editorID) {
  740. // only for currently selected editor
  741. var url = $form.find('#imageURL').val();
  742. var align = $form.find('select#align').val();
  743. // set default values
  744. if (!align) {
  745. align = 'center';
  746. }
  747. if (!url) {
  748. // no url set
  749. $form.prepend($('<div />', { style: 'color:red;display:none;', class: 'form-item is-error', text: settings.translations.pleaseSelectImage }));
  750. $form.children('.form-item.is-error').slideDown();
  751. setTimeout(function() {
  752. $form.children('.form-item.is-error').slideUp(function() {
  753. $(this).remove();
  754. });
  755. }, 5000);
  756. } else {
  757. // write html in editor
  758. var html = '';
  759. if (settings.useSingleQuotes === true) {
  760. if (align === "center") {
  761. html = "<div style='text-align:center;'><img src='" + url + "'></div>";
  762. } else {
  763. html = "<img src='" + url + "' align='" + align + "'>";
  764. }
  765. } else {
  766. if (align === "center") {
  767. html = '<div style="text-align:center;"><img src="' + url + '"></div>';
  768. } else {
  769. html = '<img src="' + url + '" align="' + align + '">';
  770. }
  771. }
  772. restoreSelection(editorID, true);
  773. var $editNode = $('.richText-editNode');
  774. if ($editNode.length > 0 && $editNode.prop("tagName") === "IMG") {
  775. $editNode.attr("src", url);
  776. if ($editNode.parent('div').length > 0 && $editNode.parent('div').attr('style') === 'text-align:center;' && align !== 'center') {
  777. $editNode.unwrap('div');
  778. $editNode.attr('align', align);
  779. } else if (($editNode.parent('div').length === 0 || $editNode.parent('div').attr('style') !== 'text-align:center;') && align === 'center') {
  780. $editNode.wrap('<div style="text-align:center;"></div>');
  781. $editNode.removeAttr('align');
  782. } else {
  783. $editNode.attr('align', align);
  784. }
  785. $editNode.removeClass('richText-editNode');
  786. if ($editNode.attr('class') === '') {
  787. $editNode.removeAttr('class');
  788. }
  789. } else {
  790. pasteHTMLAtCaret(html);
  791. }
  792. // reset input values
  793. $form.find('input#imageURL').val('');
  794. $('.richText-toolbar li.is-selected').removeClass("is-selected");
  795. }
  796. }
  797. });
  798. // adding file
  799. $(document).on("click", "#richText-File button.btn", function(event) {
  800. event.preventDefault();
  801. var $button = $(this);
  802. var $form = $button.parent('.richText-form-item').parent('.richText-form');
  803. if ($form.attr("data-editor") === editorID) {
  804. // only for currently selected editor
  805. var url = $form.find('#fileURL').val();
  806. var text = $form.find('#fileText').val();
  807. // set default values
  808. if (!text) {
  809. text = url;
  810. }
  811. if (!url) {
  812. // no url set
  813. $form.prepend($('<div />', { style: 'color:red;display:none;', class: 'form-item is-error', text: settings.translations.pleaseSelectFile }));
  814. $form.children('.form-item.is-error').slideDown();
  815. setTimeout(function() {
  816. $form.children('.form-item.is-error').slideUp(function() {
  817. $(this).remove();
  818. });
  819. }, 5000);
  820. } else {
  821. // write html in editor
  822. var html = '';
  823. if (settings.useSingleQuotes === true) {
  824. html = "<a href='" + url + "' target='_blank'>" + text + "</a>";
  825. } else {
  826. html = '<a href="' + url + '" target="_blank">' + text + '</a>';
  827. }
  828. restoreSelection(editorID, true);
  829. pasteHTMLAtCaret(html);
  830. // reset input values
  831. $form.find('input#fileURL').val('');
  832. $form.find('input#fileText').val('');
  833. $('.richText-toolbar li.is-selected').removeClass("is-selected");
  834. }
  835. }
  836. });
  837. // adding table
  838. $(document).on("click", "#richText-Table button.btn", function(event) {
  839. event.preventDefault();
  840. var $button = $(this);
  841. var $form = $button.parent('.richText-form-item').parent('.richText-form');
  842. if ($form.attr("data-editor") === editorID) {
  843. // only for currently selected editor
  844. var rows = $form.find('input#tableRows').val();
  845. var columns = $form.find('input#tableColumns').val();
  846. // set default values
  847. if (!rows || rows <= 0) {
  848. rows = 2;
  849. }
  850. if (!columns || columns <= 0) {
  851. columns = 2;
  852. }
  853. // generate table
  854. var html = '';
  855. if (settings.useSingleQuotes === true) {
  856. html = "<table class='table-1'><tbody>";
  857. } else {
  858. html = '<table class="table-1"><tbody>';
  859. }
  860. for (var i = 1; i <= rows; i++) {
  861. // start new row
  862. html += '<tr>';
  863. for (var n = 1; n <= columns; n++) {
  864. // start new column in row
  865. html += '<td> </td>';
  866. }
  867. html += '</tr>';
  868. }
  869. html += '</tbody></table>';
  870. // write html in editor
  871. restoreSelection(editorID, true);
  872. pasteHTMLAtCaret(html);
  873. // reset input values
  874. $form.find('input#tableColumns').val('');
  875. $form.find('input#tableRows').val('');
  876. $('.richText-toolbar li.is-selected').removeClass("is-selected");
  877. }
  878. });
  879. // opening / closing toolbar dropdown
  880. $(document).on("click", function(event) {
  881. var $clickedElement = $(event.target);
  882. if ($clickedElement.parents('.richText-toolbar').length === 0) {
  883. // element not in toolbar
  884. // ignore
  885. } else if ($clickedElement.hasClass("richText-dropdown-outer")) {
  886. // closing dropdown by clicking inside the editor
  887. $clickedElement.parent('a').parent('li').removeClass("is-selected");
  888. } else if ($clickedElement.find(".richText").length > 0) {
  889. // closing dropdown by clicking outside of the editor
  890. $('.richText-toolbar li').removeClass("is-selected");
  891. } else if ($clickedElement.parent().hasClass("richText-dropdown-close")) {
  892. // closing dropdown by clicking on the close button
  893. $('.richText-toolbar li').removeClass("is-selected");
  894. } else if ($clickedElement.hasClass("richText-btn") && $(event.target).children('.richText-dropdown-outer').length > 0) {
  895. // opening dropdown by clicking on toolbar button
  896. $clickedElement.parent('li').addClass("is-selected");
  897. if ($clickedElement.children('.fa,svg').hasClass("fa-link")) {
  898. // put currently selected text in URL form to replace it
  899. restoreSelection(editorID, false, true);
  900. var selectedText = getSelectedText();
  901. $clickedElement.find("input#urlText").val('');
  902. $clickedElement.find("input#url").val('');
  903. if (selectedText) {
  904. $clickedElement.find("input#urlText").val(selectedText);
  905. }
  906. } else if ($clickedElement.hasClass("fa-image")) {
  907. // image
  908. }
  909. }
  910. });
  911. // Executing editor commands
  912. $(document).on("click", ".richText-toolbar a[data-command]", function(event) {
  913. var $button = $(this);
  914. var $toolbar = $button.closest('.richText-toolbar');
  915. var $editor = $toolbar.siblings('.richText-editor');
  916. var id = $editor.attr("id");
  917. if ($editor.length > 0 && id === editorID && (!$button.parent("li").attr('data-disable') || $button.parent("li").attr('data-disable') === "false")) {
  918. event.preventDefault();
  919. var command = $(this).data("command");
  920. if (command === "toggleCode") {
  921. toggleCode($editor.attr("id"));
  922. } else {
  923. var option = null;
  924. if ($(this).data('option')) {
  925. option = $(this).data('option').toString();
  926. if (option.match(/^h[1-6]$/)) {
  927. command = "heading";
  928. }
  929. }
  930. formatText(command, option, id);
  931. if (command === "removeFormat") {
  932. // remove HTML/CSS formatting
  933. $editor.find('*').each(function() {
  934. // remove all, but very few, attributes from the nodes
  935. var keepAttributes = [
  936. "id", "class",
  937. "name", "action", "method",
  938. "src", "align", "alt", "title",
  939. "style", "webkitallowfullscreen", "mozallowfullscreen", "allowfullscreen",
  940. "width", "height", "frameborder"
  941. ];
  942. var element = $(this);
  943. var attributes = $.map(this.attributes, function(item) {
  944. return item.name;
  945. });
  946. $.each(attributes, function(i, item) {
  947. if (keepAttributes.indexOf(item) < 0 && item.substr(0, 5) !== 'data-') {
  948. element.removeAttr(item);
  949. }
  950. });
  951. if (element.prop('tagName') === "A") {
  952. // remove empty URL tags
  953. element.replaceWith(function() {
  954. return $('<span />', { html: $(this).html() });
  955. });
  956. }
  957. });
  958. formatText('formatBlock', 'div', id);
  959. }
  960. // clean up empty tags, which can be created while replacing formatting or when copy-pasting from other tools
  961. $editor.find('div:empty,p:empty,li:empty,h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty').remove();
  962. $editor.find('h1,h2,h3,h4,h5,h6').unwrap('h1,h2,h3,h4,h5,h6');
  963. }
  964. }
  965. // close dropdown after click
  966. $button.parents('li.is-selected').removeClass('is-selected');
  967. });
  968. /** INTERNAL METHODS **/
  969. /**
  970. * Format text in editor
  971. * @param {string} command
  972. * @param {string|null} option
  973. * @param {string} editorID
  974. * @private
  975. */
  976. function formatText(command, option, editorID) {
  977. if (typeof option === "undefined") {
  978. option = null;
  979. }
  980. // restore selection from before clicking on any button
  981. doRestore(editorID);
  982. // Temporarily enable designMode so that
  983. // document.execCommand() will work
  984. // document.designMode = "ON";
  985. // Execute the command
  986. if (command === "heading" && getSelectedText()) {
  987. // IE workaround
  988. pasteHTMLAtCaret('<' + option + '>' + getSelectedText() + '</' + option + '>');
  989. } else if (command === "fontSize" && parseInt(option) > 0) {
  990. var selection = getSelectedText();
  991. selection = (selection + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + '<br>' + '$2');
  992. var html = (settings.useSingleQuotes ? "<span style='font-size:" + option + "px;'>" + selection + "</span>" : '<span style="font-size:' + option + 'px;">' + selection + '</span>');
  993. pasteHTMLAtCaret(html);
  994. } else {
  995. document.execCommand(command, false, option);
  996. }
  997. // Disable designMode
  998. // document.designMode = "OFF";
  999. }
  1000. /**
  1001. * Update textarea when updating editor
  1002. * @private
  1003. */
  1004. function updateTextarea() {
  1005. var $editor = $('#' + editorID);
  1006. var content = $editor.html();
  1007. if (settings.useSingleQuotes === true) {
  1008. content = changeAttributeQuotes(content);
  1009. }
  1010. $editor.siblings('.richText-initial').val(content);
  1011. }
  1012. /**
  1013. * Update editor when updating textarea
  1014. * @private
  1015. */
  1016. function updateEditor(editorID) {
  1017. var $editor = $('#' + editorID);
  1018. var content = $editor.siblings('.richText-initial').val();
  1019. $editor.html(content);
  1020. }
  1021. /**
  1022. * Save caret position and selection
  1023. * @return object
  1024. **/
  1025. function saveSelection(editorID) {
  1026. var containerEl = document.getElementById(editorID);
  1027. var range, start, end, type;
  1028. if (window.getSelection && document.createRange) {
  1029. var sel = window.getSelection && window.getSelection();
  1030. if (sel && sel.rangeCount > 0 && $(sel.anchorNode).parents('#' + editorID).length > 0) {
  1031. range = window.getSelection().getRangeAt(0);
  1032. var preSelectionRange = range.cloneRange();
  1033. preSelectionRange.selectNodeContents(containerEl);
  1034. preSelectionRange.setEnd(range.startContainer, range.startOffset);
  1035. start = preSelectionRange.toString().length;
  1036. end = (start + range.toString().length);
  1037. type = (start === end ? 'caret' : 'selection');
  1038. anchor = sel.anchorNode; //(type === "caret" && sel.anchorNode.tagName ? sel.anchorNode : false);
  1039. start = (type === 'caret' && anchor !== false ? 0 : preSelectionRange.toString().length);
  1040. end = (type === 'caret' && anchor !== false ? 0 : (start + range.toString().length));
  1041. return {
  1042. start: start,
  1043. end: end,
  1044. type: type,
  1045. anchor: anchor,
  1046. editorID: editorID
  1047. }
  1048. }
  1049. }
  1050. return (savedSelection ? savedSelection : {
  1051. start: 0,
  1052. end: 0
  1053. });
  1054. }
  1055. /**
  1056. * Restore selection
  1057. **/
  1058. function restoreSelection(editorID, media, url) {
  1059. var containerEl = document.getElementById(editorID);
  1060. var savedSel = savedSelection;
  1061. if (!savedSel) {
  1062. // fix selection if editor has not been focused
  1063. savedSel = {
  1064. 'start': 0,
  1065. 'end': 0,
  1066. 'type': 'caret',
  1067. 'editorID': editorID,
  1068. 'anchor': $('#' + editorID).children('div')[0]
  1069. };
  1070. }
  1071. if (savedSel.editorID !== editorID) {
  1072. return false;
  1073. } else if (media === true) {
  1074. containerEl = (savedSel.anchor ? savedSel.anchor : containerEl); // fix selection issue
  1075. } else if (url === true) {
  1076. if (savedSel.start === 0 && savedSel.end === 0) {
  1077. containerEl = (savedSel.anchor ? savedSel.anchor : containerEl); // fix selection issue
  1078. }
  1079. }
  1080. if (window.getSelection && document.createRange) {
  1081. var charIndex = 0,
  1082. range = document.createRange();
  1083. if (!range || !containerEl) { window.getSelection().removeAllRanges(); return true; }
  1084. range.setStart(containerEl, 0);
  1085. range.collapse(true);
  1086. var nodeStack = [containerEl],
  1087. node, foundStart = false,
  1088. stop = false;
  1089. while (!stop && (node = nodeStack.pop())) {
  1090. if (node.nodeType === 3) {
  1091. var nextCharIndex = charIndex + node.length;
  1092. if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
  1093. range.setStart(node, savedSel.start - charIndex);
  1094. foundStart = true;
  1095. }
  1096. if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
  1097. range.setEnd(node, savedSel.end - charIndex);
  1098. stop = true;
  1099. }
  1100. charIndex = nextCharIndex;
  1101. } else {
  1102. var i = node.childNodes.length;
  1103. while (i--) {
  1104. nodeStack.push(node.childNodes[i]);
  1105. }
  1106. }
  1107. }
  1108. var sel = window.getSelection();
  1109. sel.removeAllRanges();
  1110. sel.addRange(range);
  1111. }
  1112. }
  1113. /**
  1114. * Save caret position and selection
  1115. * @return object
  1116. **/
  1117. /*
  1118. function saveSelection(editorID) {
  1119. var containerEl = document.getElementById(editorID);
  1120. var start;
  1121. if (window.getSelection && document.createRange) {
  1122. var sel = window.getSelection && window.getSelection();
  1123. if (sel && sel.rangeCount > 0) {
  1124. var range = window.getSelection().getRangeAt(0);
  1125. var preSelectionRange = range.cloneRange();
  1126. preSelectionRange.selectNodeContents(containerEl);
  1127. preSelectionRange.setEnd(range.startContainer, range.startOffset);
  1128. start = preSelectionRange.toString().length;
  1129. return {
  1130. start: start,
  1131. end: start + range.toString().length,
  1132. editorID: editorID
  1133. }
  1134. } else {
  1135. return (savedSelection ? savedSelection : {
  1136. start: 0,
  1137. end: 0
  1138. });
  1139. }
  1140. } else if (document.selection && document.body.createTextRange) {
  1141. var selectedTextRange = document.selection.createRange();
  1142. var preSelectionTextRange = document.body.createTextRange();
  1143. preSelectionTextRange.moveToElementText(containerEl);
  1144. preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
  1145. start = preSelectionTextRange.text.length;
  1146. return {
  1147. start: start,
  1148. end: start + selectedTextRange.text.length,
  1149. editorID: editorID
  1150. };
  1151. }
  1152. }
  1153. */
  1154. /**
  1155. * Restore selection
  1156. **/
  1157. /*
  1158. function restoreSelection(editorID) {
  1159. var containerEl = document.getElementById(editorID);
  1160. var savedSel = savedSelection;
  1161. if(savedSel.editorID !== editorID) {
  1162. return false;
  1163. }
  1164. if (window.getSelection && document.createRange) {
  1165. var charIndex = 0, range = document.createRange();
  1166. range.setStart(containerEl, 0);
  1167. range.collapse(true);
  1168. var nodeStack = [containerEl], node, foundStart = false, stop = false;
  1169. while (!stop && (node = nodeStack.pop())) {
  1170. if (node.nodeType === 3) {
  1171. var nextCharIndex = charIndex + node.length;
  1172. if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
  1173. range.setStart(node, savedSel.start - charIndex);
  1174. foundStart = true;
  1175. }
  1176. if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
  1177. range.setEnd(node, savedSel.end - charIndex);
  1178. stop = true;
  1179. }
  1180. charIndex = nextCharIndex;
  1181. } else {
  1182. var i = node.childNodes.length;
  1183. while (i--) {
  1184. nodeStack.push(node.childNodes[i]);
  1185. }
  1186. }
  1187. }
  1188. var sel = window.getSelection();
  1189. sel.removeAllRanges();
  1190. sel.addRange(range);
  1191. } else if (document.selection && document.body.createTextRange) {
  1192. var textRange = document.body.createTextRange();
  1193. textRange.moveToElementText(containerEl);
  1194. textRange.collapse(true);
  1195. textRange.moveEnd("character", savedSel.end);
  1196. textRange.moveStart("character", savedSel.start);
  1197. textRange.select();
  1198. }
  1199. }
  1200. */
  1201. /**
  1202. * Enables tabbing/shift-tabbing between contentEditable table cells
  1203. * @param {Window} win - Active window context.
  1204. * @param {Event} e - jQuery Event object for the keydown that fired.
  1205. */
  1206. function tabifyEditableTable(win, e) {
  1207. if (e.keyCode !== 9) {
  1208. return false;
  1209. }
  1210. var sel;
  1211. if (win.getSelection) {
  1212. sel = win.getSelection();
  1213. if (sel.rangeCount > 0) {
  1214. var textNode = null,
  1215. direction = null;
  1216. if (!e.shiftKey) {
  1217. direction = "next";
  1218. textNode = (sel.focusNode.nodeName === "TD") ?
  1219. (sel.focusNode.nextSibling != null) ?
  1220. sel.focusNode.nextSibling :
  1221. (sel.focusNode.parentNode.nextSibling != null) ?
  1222. sel.focusNode.parentNode.nextSibling.childNodes[0] :
  1223. null :
  1224. (sel.focusNode.parentNode.nextSibling != null) ?
  1225. sel.focusNode.parentNode.nextSibling :
  1226. (sel.focusNode.parentNode.parentNode.nextSibling != null) ?
  1227. sel.focusNode.parentNode.parentNode.nextSibling.childNodes[0] :
  1228. null;
  1229. } else {
  1230. direction = "previous";
  1231. textNode = (sel.focusNode.nodeName === "TD") ?
  1232. (sel.focusNode.previousSibling != null) ?
  1233. sel.focusNode.previousSibling :
  1234. (sel.focusNode.parentNode.previousSibling != null) ?
  1235. sel.focusNode.parentNode.previousSibling.childNodes[sel.focusNode.parentNode.previousSibling.childNodes.length - 1] :
  1236. null :
  1237. (sel.focusNode.parentNode.previousSibling != null) ?
  1238. sel.focusNode.parentNode.previousSibling :
  1239. (sel.focusNode.parentNode.parentNode.previousSibling != null) ?
  1240. sel.focusNode.parentNode.parentNode.previousSibling.childNodes[sel.focusNode.parentNode.parentNode.previousSibling.childNodes.length - 1] :
  1241. null;
  1242. }
  1243. if (textNode != null) {
  1244. sel.collapse(textNode, Math.min(textNode.length, sel.focusOffset + 1));
  1245. if (textNode.textContent != null) {
  1246. sel.selectAllChildren(textNode);
  1247. }
  1248. e.preventDefault();
  1249. return true;
  1250. } else if (textNode === null && direction === "next" && sel.focusNode.nodeName === "TD") {
  1251. // add new row on TAB if arrived at the end of the row
  1252. var $table = $(sel.focusNode).parents("table");
  1253. var cellsPerLine = $table.find("tr").first().children("td").length;
  1254. var $tr = $("<tr />");
  1255. var $td = $("<td />");
  1256. for (var i = 1; i <= cellsPerLine; i++) {
  1257. $tr.append($td.clone());
  1258. }
  1259. $table.append($tr);
  1260. // simulate tabing through table
  1261. tabifyEditableTable(window, { keyCode: 9, shiftKey: false, preventDefault: function() {} });
  1262. }
  1263. }
  1264. }
  1265. return false;
  1266. }
  1267. /**
  1268. * Returns the text from the current selection
  1269. * @private
  1270. * @return {string|boolean}
  1271. */
  1272. function getSelectedText() {
  1273. var range;
  1274. if (window.getSelection) { // all browsers, except IE before version 9
  1275. range = window.getSelection();
  1276. return range.toString() ? range.toString() : range.focusNode !== null ? range.focusNode.nodeValue : '';
  1277. } else if (document.selection.createRange) { // Internet Explorer
  1278. range = document.selection.createRange();
  1279. return range.text;
  1280. }
  1281. return false;
  1282. }
  1283. /**
  1284. * Save selection
  1285. */
  1286. function doSave(editorID) {
  1287. var $textarea = $('.richText-editor#' + editorID).siblings('.richText-initial');
  1288. addHistory($textarea.val());
  1289. savedSelection = saveSelection(editorID);
  1290. }
  1291. /**
  1292. * Add to history
  1293. * @param val
  1294. */
  1295. function addHistory(val) {
  1296. if (history.length - 1 > historyPosition) {
  1297. history.length = historyPosition + 1;
  1298. }
  1299. if (history[history.length - 1] !== val) {
  1300. history.push(val);
  1301. }
  1302. historyPosition = history.length - 1;
  1303. setHistoryButtons();
  1304. }
  1305. function setHistoryButtons() {
  1306. if (historyPosition <= 0) {
  1307. $editor.find(".richText-undo").addClass("is-disabled");
  1308. } else {
  1309. $editor.find(".richText-undo").removeClass("is-disabled");
  1310. }
  1311. if (historyPosition >= history.length - 1 || history.length === 0) {
  1312. $editor.find(".richText-redo").addClass("is-disabled");
  1313. } else {
  1314. $editor.find(".richText-redo").removeClass("is-disabled");
  1315. }
  1316. }
  1317. /**
  1318. * Undo
  1319. */
  1320. function undo() {
  1321. historyPosition--;
  1322. var value = history[historyPosition];
  1323. $editor.find('textarea').val(value);
  1324. $editor.find('.richText-editor').html(value);
  1325. setHistoryButtons();
  1326. }
  1327. /**
  1328. * Undo
  1329. */
  1330. function redo() {
  1331. historyPosition++;
  1332. var value = history[historyPosition];
  1333. $editor.find('textarea').val(value);
  1334. $editor.find('.richText-editor').html(value);
  1335. setHistoryButtons();
  1336. }
  1337. /**
  1338. * Restore selection
  1339. */
  1340. function doRestore(id) {
  1341. if (savedSelection) {
  1342. restoreSelection((id ? id : savedSelection.editorID));
  1343. }
  1344. }
  1345. /**
  1346. * Paste HTML at caret position
  1347. * @param {string} html HTML code
  1348. * @private
  1349. */
  1350. function pasteHTMLAtCaret(html) {
  1351. // add HTML code for Internet Explorer
  1352. var sel, range;
  1353. if (window.getSelection) {
  1354. // IE9 and non-IE
  1355. sel = window.getSelection();
  1356. if (sel.getRangeAt && sel.rangeCount) {
  1357. range = sel.getRangeAt(0);
  1358. range.deleteContents();
  1359. // Range.createContextualFragment() would be useful here but is
  1360. // only relatively recently standardized and is not supported in
  1361. // some browsers (IE9, for one)
  1362. var el = document.createElement("div");
  1363. el.innerHTML = html;
  1364. var frag = document.createDocumentFragment(),
  1365. node, lastNode;
  1366. while ((node = el.firstChild)) {
  1367. lastNode = frag.appendChild(node);
  1368. }
  1369. range.insertNode(frag);
  1370. // Preserve the selection
  1371. if (lastNode) {
  1372. range = range.cloneRange();
  1373. range.setStartAfter(lastNode);
  1374. range.collapse(true);
  1375. sel.removeAllRanges();
  1376. sel.addRange(range);
  1377. }
  1378. }
  1379. } else if (document.selection && document.selection.type !== "Control") {
  1380. // IE < 9
  1381. document.selection.createRange().pasteHTML(html);
  1382. }
  1383. }
  1384. /**
  1385. * Change quotes around HTML attributes
  1386. * @param {string} string
  1387. * @return {string}
  1388. */
  1389. function changeAttributeQuotes(string) {
  1390. if (!string) {
  1391. return '';
  1392. }
  1393. var regex;
  1394. var rstring;
  1395. if (settings.useSingleQuotes === true) {
  1396. regex = /\s+(\w+\s*=\s*(["][^"]*["])|(['][^']*[']))+/g;
  1397. rstring = string.replace(regex, function($0, $1, $2) {
  1398. if (!$2) { return $0; }
  1399. return $0.replace($2, $2.replace(/\"/g, "'"));
  1400. });
  1401. } else {
  1402. regex = /\s+(\w+\s*=\s*(['][^']*['])|(["][^"]*["]))+/g;
  1403. rstring = string.replace(regex, function($0, $1, $2) {
  1404. if (!$2) { return $0; }
  1405. return $0.replace($2, $2.replace(/'/g, '"'));
  1406. });
  1407. }
  1408. return rstring;
  1409. }
  1410. /**
  1411. * Load colors for font or background
  1412. * @param {string} command Command
  1413. * @returns {string}
  1414. * @private
  1415. */
  1416. function loadColors(command) {
  1417. var colors = [];
  1418. var result = '';
  1419. colors["#FFFFFF"] = settings.translations.white;
  1420. colors["#000000"] = settings.translations.black;
  1421. colors["#7F6000"] = settings.translations.brown;
  1422. colors["#938953"] = settings.translations.beige;
  1423. colors["#1F497D"] = settings.translations.darkBlue;
  1424. colors["blue"] = settings.translations.blue;
  1425. colors["#4F81BD"] = settings.translations.lightBlue;
  1426. colors["#953734"] = settings.translations.darkRed;
  1427. colors["red"] = settings.translations.red;
  1428. colors["#4F6128"] = settings.translations.darkGreen;
  1429. colors["green"] = settings.translations.green;
  1430. colors["#3F3151"] = settings.translations.purple;
  1431. colors["#31859B"] = settings.translations.darkTurquois;
  1432. colors["#4BACC6"] = settings.translations.turquois;
  1433. colors["#E36C09"] = settings.translations.darkOrange;
  1434. colors["#F79646"] = settings.translations.orange;
  1435. colors["#FFFF00"] = settings.translations.yellow;
  1436. if (settings.colors && settings.colors.length > 0) {
  1437. colors = settings.colors;
  1438. }
  1439. for (var i in colors) {
  1440. 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>';
  1441. }
  1442. return result;
  1443. }
  1444. /**
  1445. * Toggle (show/hide) code or editor
  1446. * @private
  1447. */
  1448. function toggleCode(editorID) {
  1449. doRestore(editorID);
  1450. if ($editor.find('.richText-editor').is(":visible")) {
  1451. // show code
  1452. $editor.find('.richText-initial').show();
  1453. $editor.find('.richText-editor').hide();
  1454. // disable non working buttons
  1455. $('.richText-toolbar').find('.richText-btn').each(function() {
  1456. if ($(this).children('.fa-code').length === 0) {
  1457. $(this).parent('li').attr("data-disable", "true");
  1458. }
  1459. });
  1460. convertCaretPosition(editorID, savedSelection);
  1461. } else {
  1462. // show editor
  1463. $editor.find('.richText-initial').hide();
  1464. $editor.find('.richText-editor').show();
  1465. convertCaretPosition(editorID, savedSelection, true);
  1466. // enable all buttons again
  1467. $('.richText-toolbar').find('li').removeAttr("data-disable");
  1468. }
  1469. }
  1470. /**
  1471. * Convert caret position from editor to code view (or in reverse)
  1472. * @param {string} editorID
  1473. * @param {object} selection
  1474. * @param {boolean} reverse
  1475. **/
  1476. function convertCaretPosition(editorID, selection, reverse) {
  1477. var $editor = $('#' + editorID);
  1478. var $textarea = $editor.siblings(".richText-initial");
  1479. var code = $textarea.val();
  1480. if (!selection ||  !code) {
  1481. return { start: 0, end: 0 };
  1482. }
  1483. if (reverse === true) {
  1484. savedSelection = { start: $editor.text().length, end: $editor.text().length, editorID: editorID };
  1485. restoreSelection(editorID);
  1486. return true;
  1487. }
  1488. selection.node = $textarea[0];
  1489. var states = { start: false, end: false, tag: false, isTag: false, tagsCount: 0, isHighlight: (selection.start !== selection.end) };
  1490. for (var i = 0; i < code.length; i++) {
  1491. if (code[i] === "<") {
  1492. // HTML tag starts
  1493. states.isTag = true;
  1494. states.tag = false;
  1495. states.tagsCount++;
  1496. } else if (states.isTag === true && code[i] !== ">") {
  1497. states.tagsCount++;
  1498. } else if (states.isTag === true && code[i] === ">") {
  1499. states.isTag = false;
  1500. states.tag = true;
  1501. states.tagsCount++;
  1502. } else if (states.tag === true) {
  1503. states.tag = false;
  1504. }
  1505. if (!reverse) {
  1506. if ((selection.start + states.tagsCount) <= i && states.isHighlight && !states.isTag && !states.tag && !states.start) {
  1507. selection.start = i;
  1508. states.start = true;
  1509. } else if ((selection.start + states.tagsCount) <= i + 1 && !states.isHighlight && !states.isTag && !states.tag && !states.start) {
  1510. selection.start = i + 1;
  1511. states.start = true;
  1512. }
  1513. if ((selection.end + states.tagsCount) <= i + 1 && !states.isTag && !states.tag && !states.end) {
  1514. selection.end = i + 1;
  1515. states.end = true;
  1516. }
  1517. }
  1518. }
  1519. createSelection(selection.node, selection.start, selection.end);
  1520. return selection;
  1521. }
  1522. /**
  1523. * Create selection on node element
  1524. * @param {Node} field
  1525. * @param {int} start
  1526. * @param {int} end
  1527. **/
  1528. function createSelection(field, start, end) {
  1529. if (field.createTextRange) {
  1530. var selRange = field.createTextRange();
  1531. selRange.collapse(true);
  1532. selRange.moveStart('character', start);
  1533. selRange.moveEnd('character', end);
  1534. selRange.select();
  1535. field.focus();
  1536. } else if (field.setSelectionRange) {
  1537. field.focus();
  1538. field.setSelectionRange(start, end);
  1539. } else if (typeof field.selectionStart != 'undefined') {
  1540. field.selectionStart = start;
  1541. field.selectionEnd = end;
  1542. field.focus();
  1543. }
  1544. }
  1545. /**
  1546. * Get video embed code from URL
  1547. * @param {string} url Video URL
  1548. * @param {string} size Size in the form of widthxheight
  1549. * @return {string|boolean}
  1550. * @private
  1551. **/
  1552. function getVideoCode(url, size) {
  1553. var video = getVideoID(url);
  1554. var responsive = false,
  1555. success = false;
  1556. if (!video) {
  1557. // video URL not supported
  1558. return false;
  1559. }
  1560. if (!size) {
  1561. size = "640x360";
  1562. size = size.split("x");
  1563. } else if (size !== "responsive") {
  1564. size = size.split("x");
  1565. } else {
  1566. responsive = true;
  1567. size = "640x360";
  1568. size = size.split("x");
  1569. }
  1570. var html = '<br><br>';
  1571. if (responsive === true) {
  1572. html += '<div style="position:relative;height:0;padding-bottom:56.25%">';
  1573. }
  1574. var allowfullscreen = 'webkitallowfullscreen mozallowfullscreen allowfullscreen';
  1575. if (video.platform === "YouTube") {
  1576. var youtubeDomain = (settings.youtubeCookies ? 'www.youtube.com' : 'www.youtube-nocookie.com');
  1577. 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>';
  1578. success = true;
  1579. } else if (video.platform === "Vimeo") {
  1580. 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>';
  1581. success = true;
  1582. } else if (video.platform === "Facebook") {
  1583. 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>';
  1584. success = true;
  1585. } else if (video.platform === "Dailymotion") {
  1586. 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>';
  1587. success = true;
  1588. }
  1589. if (responsive === true) {
  1590. html += '</div>';
  1591. }
  1592. html += '<br><br>';
  1593. if (success) {
  1594. return html;
  1595. }
  1596. return false;
  1597. }
  1598. /**
  1599. * Returns the unique video ID
  1600. * @param {string} url
  1601. * return {object|boolean}
  1602. **/
  1603. function getVideoID(url) {
  1604. var vimeoRegExp = /(?:http?s?:\/\/)?(?:www\.)?(?:vimeo\.com)\/?(.+)/;
  1605. var youtubeRegExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
  1606. var facebookRegExp = /(?:http?s?:\/\/)?(?:www\.)?(?:facebook\.com)\/.*\/videos\/[0-9]+/;
  1607. var dailymotionRegExp = /(?:http?s?:\/\/)?(?:www\.)?(?:dailymotion\.com)\/video\/([a-zA-Z0-9]+)/;
  1608. var youtubeMatch = url.match(youtubeRegExp);
  1609. var vimeoMatch = url.match(vimeoRegExp);
  1610. var facebookMatch = url.match(facebookRegExp);
  1611. var dailymotionMatch = url.match(dailymotionRegExp);
  1612. if (youtubeMatch && youtubeMatch[2].length === 11) {
  1613. return {
  1614. "platform": "YouTube",
  1615. "id": youtubeMatch[2]
  1616. };
  1617. } else if (vimeoMatch && vimeoMatch[1]) {
  1618. return {
  1619. "platform": "Vimeo",
  1620. "id": vimeoMatch[1]
  1621. };
  1622. } else if (facebookMatch && facebookMatch[0]) {
  1623. return {
  1624. "platform": "Facebook",
  1625. "id": facebookMatch[0]
  1626. };
  1627. } else if (dailymotionMatch && dailymotionMatch[1]) {
  1628. return {
  1629. "platform": "Dailymotion",
  1630. "id": dailymotionMatch[1]
  1631. };
  1632. }
  1633. return false;
  1634. }
  1635. /**
  1636. * Fix the first line as by default the first line has no tag container
  1637. */
  1638. function fixFirstLine() {
  1639. if ($editor && !$editor.find(".richText-editor").html()) {
  1640. // set first line with the right tags
  1641. if (settings.useParagraph !== false) {
  1642. $editor.find(".richText-editor").html('<p><br></p>');
  1643. } else {
  1644. $editor.find(".richText-editor").html('<div><br></div>');
  1645. }
  1646. } else {
  1647. // replace tags, to force <div> or <p> tags and fix issues
  1648. if (settings.useParagraph !== false) {
  1649. $editor.find(".richText-editor").find('div').replaceWith(function() {
  1650. return $('<p />', { html: $(this).html() });
  1651. });
  1652. } else {
  1653. $editor.find(".richText-editor").find('p').replaceWith(function() {
  1654. return $('<div />', { html: $(this).html() });
  1655. });
  1656. }
  1657. }
  1658. updateTextarea();
  1659. }
  1660. return $(this);
  1661. };
  1662. $.fn.unRichText = function(options) {
  1663. // set default options
  1664. // and merge them with the parameter options
  1665. var settings = $.extend({
  1666. delay: 0 // delay in ms
  1667. }, options);
  1668. var $editor, $textarea, $main;
  1669. var $el = $(this);
  1670. /**
  1671. * Initialize undoing RichText and call remove() method
  1672. */
  1673. function init() {
  1674. if ($el.hasClass('richText')) {
  1675. $main = $el;
  1676. } else if ($el.hasClass('richText-initial') || $el.hasClass('richText-editor')) {
  1677. $main = $el.parents('.richText');
  1678. }
  1679. if (!$main) {
  1680. // node element does not correspond to RichText elements
  1681. return false;
  1682. }
  1683. $editor = $main.find('.richText-editor');
  1684. $textarea = $main.find('.richText-initial');
  1685. if (parseInt(settings.delay) > 0) {
  1686. // a delay has been set
  1687. setTimeout(remove, parseInt(settings.delay));
  1688. } else {
  1689. remove();
  1690. }
  1691. }
  1692. init();
  1693. /**
  1694. * Remove RichText elements
  1695. */
  1696. function remove() {
  1697. $main.find('.richText-toolbar').remove();
  1698. $main.find('.richText-editor').remove();
  1699. $textarea
  1700. .unwrap('.richText')
  1701. .data('editor', 'richText')
  1702. .removeClass('richText-initial')
  1703. .show();
  1704. }
  1705. };
  1706. }(jQuery));