OTRS API Reference JavaScript

Source: Core.UI.RichTextEditor.js

  1. // --
  2. // Copyright (C) 2001-2018 OTRS AG, https://otrs.com/
  3. // --
  4. // This software comes with ABSOLUTELY NO WARRANTY. For details, see
  5. // the enclosed file COPYING for license information (GPL). If you
  6. // did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
  7. // --
  8. "use strict";
  9. var Core = Core || {};
  10. Core.UI = Core.UI || {};
  11. /**
  12. * @namespace Core.UI.RichTextEditor
  13. * @memberof Core.UI
  14. * @author OTRS AG
  15. * @description
  16. * Richtext Editor.
  17. */
  18. Core.UI.RichTextEditor = (function (TargetNS) {
  19. /**
  20. * @private
  21. * @name $FormID
  22. * @memberof Core.UI.RichTextEditor
  23. * @member {jQueryObject}
  24. * @description
  25. * Hidden input field with name FormID.
  26. */
  27. var $FormID,
  28. /**
  29. * @private
  30. * @name TimeOutRTEOnChange
  31. * @memberof Core.UI.RichTextEditor
  32. * @member {Object}
  33. * @description
  34. * Object to handle timeout.
  35. */
  36. TimeOutRTEOnChange;
  37. /**
  38. * @private
  39. * @name CheckFormID
  40. * @memberof Core.UI.RichTextEditor
  41. * @function
  42. * @returns {jQueryObject} FormID element.
  43. * @param {jQueryObject} $EditorArea - The jQuery object of the element that has become a rich text editor.
  44. * @description
  45. * Check in the window which hidden element has a name same to 'FormID' and return it like a JQuery object.
  46. */
  47. function CheckFormID($EditorArea) {
  48. if (typeof $FormID === 'undefined') {
  49. $FormID = $EditorArea.closest('form').find('input:hidden[name=FormID]');
  50. }
  51. return $FormID;
  52. }
  53. /**
  54. * @name InitEditor
  55. * @memberof Core.UI.RichTextEditor
  56. * @function
  57. * @returns {Boolean} Returns false on error.
  58. * @param {jQueryObject} $EditorArea - The jQuery object of the element that will be a rich text editor.
  59. * @description
  60. * This function initializes the application and executes the needed functions.
  61. */
  62. TargetNS.InitEditor = function ($EditorArea) {
  63. var EditorID = '',
  64. Editor,
  65. UserLanguage,
  66. UploadURL = '',
  67. EditorConfig;
  68. if (typeof CKEDITOR === 'undefined') {
  69. return false;
  70. }
  71. if (isJQueryObject($EditorArea) && $EditorArea.hasClass('HasCKEInstance')) {
  72. return false;
  73. }
  74. if (isJQueryObject($EditorArea) && $EditorArea.length === 1) {
  75. EditorID = $EditorArea.attr('id');
  76. }
  77. if (EditorID === '') {
  78. Core.Exception.Throw('RichTextEditor: Need exactly one EditorArea!', 'TypeError');
  79. }
  80. // mark the editor textarea as linked with an RTE instance to avoid multiple instances
  81. $EditorArea.addClass('HasCKEInstance');
  82. CKEDITOR.on('instanceCreated', function (Editor) {
  83. CKEDITOR.addCss(Core.Config.Get('RichText.EditingAreaCSS'));
  84. // Remove the validation error tooltip if content is added to the editor
  85. Editor.editor.on('change', function() {
  86. window.clearTimeout(TimeOutRTEOnChange);
  87. TimeOutRTEOnChange = window.setTimeout(function () {
  88. Core.Form.Validate.ValidateElement($(Editor.editor.element.$));
  89. Core.App.Publish('Event.UI.RichTextEditor.ChangeValidationComplete', [Editor]);
  90. }, 250);
  91. });
  92. Core.App.Publish('Event.UI.RichTextEditor.InstanceCreated', [Editor]);
  93. });
  94. CKEDITOR.on('instanceReady', function (Editor) {
  95. // specific config for CodeMirror instances (e.g. XSLT editor)
  96. if (Core.Config.Get('RichText.Type') == 'CodeMirror') {
  97. // The width of a tab character. Defaults to 4.
  98. window[ 'codemirror_' + Editor.editor.id ].setOption("tabSize", 4);
  99. // How many spaces a block (whatever that means in the edited language) should be indented. The default is 2.
  100. window[ 'codemirror_' + Editor.editor.id ].setOption("indentUnit", 4);
  101. // Whether to use the context-sensitive indentation that the mode provides (or just indent the same as the line before). Defaults to true.
  102. window[ 'codemirror_' + Editor.editor.id ].setOption("tabMode", 'spaces');
  103. window[ 'codemirror_' + Editor.editor.id ].setOption("smartIndent", true);
  104. // convert tabs to spaces
  105. window[ 'codemirror_' + Editor.editor.id ].setOption("extraKeys", {
  106. Tab: function(cm) {
  107. var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
  108. cm.replaceSelection(spaces);
  109. }
  110. });
  111. }
  112. Core.App.Publish('Event.UI.RichTextEditor.InstanceReady', [Editor]);
  113. });
  114. // The format for the language is different between OTRS and CKEditor (see bug#8024)
  115. // To correct this, we replace "_" with "-" in the language (e.g. zh_CN becomes zh-cn)
  116. UserLanguage = Core.Config.Get('UserLanguage').replace(/_/, "-");
  117. // build URL for image upload
  118. if (CheckFormID($EditorArea).length) {
  119. UploadURL = Core.Config.Get('Baselink')
  120. + 'Action='
  121. + Core.Config.Get('RichText.PictureUploadAction', 'PictureUpload')
  122. + '&FormID='
  123. + CheckFormID($EditorArea).val()
  124. + '&' + Core.Config.Get('SessionName')
  125. + '=' + Core.Config.Get('SessionID');
  126. }
  127. // set default editor config, but allow custom config for other types for editors
  128. /*eslint-disable camelcase */
  129. EditorConfig = {
  130. customConfig: '', // avoid loading external config files
  131. disableNativeSpellChecker: false,
  132. defaultLanguage: UserLanguage,
  133. language: UserLanguage,
  134. width: Core.Config.Get('RichText.Width', 620),
  135. resize_minWidth: Core.Config.Get('RichText.Width', 620),
  136. height: Core.Config.Get('RichText.Height', 320),
  137. removePlugins: CheckFormID($EditorArea).length ? '' : 'image2,uploadimage',
  138. forcePasteAsPlainText: false,
  139. format_tags: 'p;h1;h2;h3;h4;h5;h6;pre',
  140. fontSize_sizes: '8px;10px;12px;16px;18px;20px;22px;24px;26px;28px;30px;',
  141. extraAllowedContent: 'div[type]{*}; img[*]; col[width]; style[*]{*}; *[id](*)',
  142. enterMode: CKEDITOR.ENTER_BR,
  143. shiftEnterMode: CKEDITOR.ENTER_BR,
  144. contentsLangDirection: Core.Config.Get('RichText.TextDir', 'ltr'),
  145. toolbar: CheckFormID($EditorArea).length ? Core.Config.Get('RichText.Toolbar') : Core.Config.Get('RichText.ToolbarWithoutImage'),
  146. filebrowserBrowseUrl: '',
  147. filebrowserUploadUrl: UploadURL,
  148. extraPlugins: 'splitquote,preventimagepaste,contextmenu_linkopen',
  149. entities: false,
  150. skin: 'moono-lisa'
  151. };
  152. /*eslint-enable camelcase */
  153. // specific config for CodeMirror instances (e.g. XSLT editor)
  154. if (Core.Config.Get('RichText.Type') == 'CodeMirror') {
  155. $.extend(EditorConfig, {
  156. /*eslint-disable camelcase */
  157. startupMode: 'source',
  158. allowedContent: true,
  159. extraPlugins: 'codemirror',
  160. codemirror: {
  161. theme: 'default',
  162. lineNumbers: true,
  163. lineWrapping: true,
  164. matchBrackets: true,
  165. autoCloseTags: true,
  166. autoCloseBrackets: true,
  167. enableSearchTools: true,
  168. enableCodeFolding: true,
  169. enableCodeFormatting: true,
  170. autoFormatOnStart: false,
  171. autoFormatOnModeChange: false,
  172. autoFormatOnUncomment: false,
  173. mode: 'htmlmixed',
  174. showTrailingSpace: true,
  175. highlightMatches: true,
  176. styleActiveLine: true
  177. }
  178. /*eslint-disable camelcase */
  179. });
  180. }
  181. Editor = CKEDITOR.replace(EditorID, EditorConfig);
  182. // check if creating CKEditor was successful
  183. // might be a problem on mobile devices e.g.
  184. if (typeof Editor !== 'undefined') {
  185. // Hack for updating the textarea with the RTE content (bug#5857)
  186. // Rename the original function to another name, than overwrite the original one
  187. CKEDITOR.instances[EditorID].updateElementOriginal = CKEDITOR.instances[EditorID].updateElement;
  188. CKEDITOR.instances[EditorID].updateElement = function() {
  189. var Data;
  190. // First call the original function
  191. CKEDITOR.instances[EditorID].updateElementOriginal();
  192. // Now check if there is actually any non-whitespace content in the
  193. // textarea field. If not, set it to an empty value to make sure
  194. // the server side validation works correctly and there is no trash
  195. // like '<br/>' stored in the DB.
  196. Data = this.element.getValue(); // get textarea content
  197. // only if codemirror plugin is not used (for XSLT editor)
  198. // or
  199. // if data contains no image tag,
  200. // this is important for inline images, we don't want to remove them!
  201. if (typeof CKEDITOR.instances[EditorID].config.codemirror === 'undefined' && !Data.match(/<img/)) {
  202. // remove tags and whitespace for checking
  203. Data = Data.replace(/\s+|&nbsp;|<\/?\w+[^>]*\/?>/g, '');
  204. if (!Data.length) {
  205. this.element.setValue(''); // reset textarea
  206. }
  207. }
  208. };
  209. // Redefine 'writeCssText' function because of unnecessary sorting of CSS properties (bug#12848).
  210. /* eslint-disable no-unused-vars */
  211. CKEDITOR.tools.writeCssText = function (styles, sort) {
  212. var name,
  213. stylesArr = [];
  214. for (name in styles)
  215. stylesArr.push(name + ':' + styles[name]);
  216. // This block sorts CSS properties which can make a wrong CSS style sent to CKEditor.
  217. // if ( sort )
  218. // stylesArr.sort();
  219. return stylesArr.join('; ');
  220. };
  221. /* eslint-enable no-unused-vars */
  222. // Needed for clientside validation of RTE
  223. CKEDITOR.instances[EditorID].on('blur', function () {
  224. CKEDITOR.instances[EditorID].updateElement();
  225. Core.Form.Validate.ValidateElement($EditorArea);
  226. });
  227. // needed for client-side validation
  228. CKEDITOR.instances[EditorID].on('focus', function () {
  229. Core.App.Publish('Event.UI.RichTextEditor.Focus', [Editor]);
  230. if ($EditorArea.attr('class').match(/Error/)) {
  231. window.setTimeout(function () {
  232. CKEDITOR.instances[EditorID].updateElement();
  233. Core.Form.Validate.ValidateElement($EditorArea);
  234. Core.App.Publish('Event.UI.RichTextEditor.FocusValidationComplete', [Editor]);
  235. }, 0);
  236. }
  237. });
  238. // mainly needed for client-side validation
  239. $EditorArea.focus(function () {
  240. TargetNS.Focus($EditorArea);
  241. Core.UI.ScrollTo($("label[for=" + $EditorArea.attr('id') + "]"));
  242. });
  243. }
  244. };
  245. /**
  246. * @name InitAllEditors
  247. * @memberof Core.UI.RichTextEditor
  248. * @function
  249. * @description
  250. * This function initializes as a rich text editor every textarea element that containing the RichText class.
  251. */
  252. TargetNS.InitAllEditors = function () {
  253. if (typeof CKEDITOR === 'undefined') {
  254. return;
  255. }
  256. $('textarea.RichText').each(function () {
  257. TargetNS.InitEditor($(this));
  258. });
  259. };
  260. /**
  261. * @name Init
  262. * @memberof Core.UI.RichTextEditor
  263. * @function
  264. * @description
  265. * This function initializes JS functionality.
  266. */
  267. TargetNS.Init = function () {
  268. if (typeof CKEDITOR === 'undefined') {
  269. return;
  270. }
  271. TargetNS.InitAllEditors();
  272. };
  273. /**
  274. * @name GetRTE
  275. * @memberof Core.UI.RichTextEditor
  276. * @function
  277. * @returns {jQueryObject} jQuery object of the corresponsing RTE element.
  278. * @param {jQueryObject} $EditorArea - The jQuery object of the element that is a rich text editor.
  279. * @description
  280. * Get RTE jQuery element.
  281. */
  282. TargetNS.GetRTE = function ($EditorArea) {
  283. var $RTE;
  284. if (isJQueryObject($EditorArea)) {
  285. $RTE = $('#cke_' + $EditorArea.attr('id'));
  286. return ($RTE.length ? $RTE : undefined);
  287. }
  288. };
  289. /**
  290. * @name UpdateLinkedField
  291. * @memberof Core.UI.RichTextEditor
  292. * @function
  293. * @param {jQueryObject} $EditorArea - The jQuery object of the element that is a rich text editor.
  294. * @description
  295. * This function updates the linked field for a rich text editor.
  296. */
  297. TargetNS.UpdateLinkedField = function ($EditorArea) {
  298. var EditorID = '',
  299. Data,
  300. StrippedContent;
  301. if (isJQueryObject($EditorArea) && $EditorArea.length === 1) {
  302. EditorID = $EditorArea.attr('id');
  303. }
  304. if (EditorID === '') {
  305. Core.Exception.Throw('RichTextEditor: Need exactly one EditorArea!', 'TypeError');
  306. }
  307. Data = CKEDITOR.instances[EditorID].getData();
  308. StrippedContent = Data.replace(/\s+|&nbsp;|<\/?\w+[^>]*\/?>/g, '');
  309. if (StrippedContent.length === 0 && !Data.match(/<img/)) {
  310. $EditorArea.val('');
  311. }
  312. else {
  313. $EditorArea.val(Data);
  314. }
  315. };
  316. /**
  317. * @name IsEnabled
  318. * @memberof Core.UI.RichTextEditor
  319. * @function
  320. * @returns {Boolean} True if RTE is enabled, false otherwise
  321. * @param {jQueryObject} $EditorArea - The jQuery object of the element that is a rich text editor.
  322. * @description
  323. * This function check if a rich text editor is enable in this moment.
  324. */
  325. TargetNS.IsEnabled = function ($EditorArea) {
  326. if (typeof CKEDITOR === 'undefined') {
  327. return false;
  328. }
  329. if (isJQueryObject($EditorArea) && $EditorArea.length) {
  330. return (CKEDITOR.instances[$EditorArea[0].id] ? true : false);
  331. }
  332. return false;
  333. };
  334. /**
  335. * @name Focus
  336. * @memberof Core.UI.RichTextEditor
  337. * @function
  338. * @param {jQueryObject} $EditorArea - The jQuery object of the element that is a rich text editor.
  339. * @description
  340. * This function focusses the given RTE.
  341. */
  342. TargetNS.Focus = function ($EditorArea) {
  343. var EditorID = '';
  344. if (isJQueryObject($EditorArea) && $EditorArea.length === 1) {
  345. EditorID = $EditorArea.attr('id');
  346. }
  347. if (EditorID === '') {
  348. Core.Exception.Throw('RichTextEditor: Need exactly one EditorArea!', 'TypeError');
  349. }
  350. if (typeof CKEDITOR === 'object') {
  351. CKEDITOR.instances[EditorID].focus();
  352. }
  353. else {
  354. $EditorArea.focus();
  355. }
  356. };
  357. Core.Init.RegisterNamespace(TargetNS, 'APP_MODULE');
  358. return TargetNS;
  359. }(Core.UI.RichTextEditor || {}));

^ Use Elevator