richtext.js 94 KB

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