tinymce3Adapter.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /**
  6. * @deprecated use lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js instead
  7. */
  8. /* global varienGlobalEvents, tinyMceEditors, MediabrowserUtility, closeEditorPopup, Base64 */
  9. /* eslint-disable strict */
  10. define([
  11. 'jquery',
  12. 'underscore',
  13. 'tinymceDeprecated',
  14. 'mage/adminhtml/wysiwyg/tiny_mce/html5-schema',
  15. 'mage/translate',
  16. 'prototype',
  17. 'mage/adminhtml/events'
  18. ], function (jQuery, _, tinyMCE3, html5Schema) {
  19. var tinyMce3Wysiwyg = Class.create();
  20. tinyMce3Wysiwyg.prototype = {
  21. mediaBrowserOpener: null,
  22. mediaBrowserTargetElementId: null,
  23. /**
  24. * @param {*} htmlId
  25. * @param {Object} config
  26. */
  27. initialize: function (htmlId, config) {
  28. if (config.baseStaticUrl && config.baseStaticDefaultUrl) {
  29. tinyMCE3.baseURL = tinyMCE3.baseURL.replace(config.baseStaticUrl, config.baseStaticDefaultUrl);
  30. }
  31. this.id = htmlId;
  32. this.config = config;
  33. this.schema = config.schema || html5Schema;
  34. _.bindAll(
  35. this,
  36. 'beforeSetContent',
  37. 'saveContent',
  38. 'onChangeContent',
  39. 'openFileBrowser',
  40. 'updateTextArea',
  41. 'removeEvents'
  42. );
  43. varienGlobalEvents.attachEventHandler('tinymceChange', this.onChangeContent);
  44. varienGlobalEvents.attachEventHandler('tinymceBeforeSetContent', this.beforeSetContent);
  45. varienGlobalEvents.attachEventHandler('tinymceSetContent', this.updateTextArea);
  46. varienGlobalEvents.attachEventHandler('tinymceSaveContent', this.saveContent);
  47. if (typeof tinyMceEditors == 'undefined') {
  48. window.tinyMceEditors = $H({});
  49. }
  50. tinyMceEditors.set(this.id, this);
  51. },
  52. /**
  53. * @param {*} mode
  54. */
  55. setup: function (mode) {
  56. if (this.config['widget_plugin_src']) {
  57. tinyMCE3.PluginManager.load('magentowidget', 'plugins/magentowidget/editor_plugin.js');
  58. }
  59. if (this.config.plugins) {
  60. this.config.plugins.each(function (plugin) {
  61. tinyMCE3.PluginManager.load(plugin.name, plugin.src);
  62. });
  63. }
  64. if (jQuery.isReady) {
  65. tinyMCE3.dom.Event.domLoaded = true;
  66. }
  67. tinyMCE3.init(this.getSettings(mode));
  68. },
  69. /**
  70. * Remove events from instance.
  71. *
  72. * @param {String} wysiwygId
  73. */
  74. removeEvents: function (wysiwygId) {
  75. var editor = tinyMceEditors.get(wysiwygId);
  76. varienGlobalEvents.removeEventHandler('tinymceChange', editor.onChangeContent);
  77. },
  78. /**
  79. * @param {*} mode
  80. * @return {Object}
  81. */
  82. getSettings: function (mode) {
  83. var plugins = 'inlinepopups,safari,pagebreak,style,layer,table,advhr,advimage,emotions,iespell,media,searchreplace,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,noneditable', //eslint-disable-line
  84. self = this,
  85. magentoPluginsOptions, magentoPlugins, settings;
  86. if (this.config['widget_plugin_src']) {
  87. plugins = 'magentowidget,' + plugins;
  88. }
  89. magentoPluginsOptions = $H({});
  90. magentoPlugins = '';
  91. if (this.config.plugins) {
  92. this.config.plugins.each(function (plugin) {
  93. magentoPlugins = plugin.name + ',' + magentoPlugins;
  94. magentoPluginsOptions.set(plugin.name, plugin.options);
  95. });
  96. if (magentoPlugins) {
  97. plugins = '-' + magentoPlugins + plugins;
  98. }
  99. }
  100. settings = {
  101. 'entity_encoding': 'raw',
  102. mode: mode != undefined ? mode : 'none', //eslint-disable-line eqeqeq
  103. elements: this.id,
  104. theme: 'advanced',
  105. plugins: plugins,
  106. 'theme_advanced_buttons1': magentoPlugins + 'magentowidget,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect', //eslint-disable-line max-len
  107. 'theme_advanced_buttons2': 'cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,forecolor,backcolor', //eslint-disable-line max-len
  108. 'theme_advanced_buttons3': 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,iespell,media,advhr,|,ltr,rtl,|,fullscreen', //eslint-disable-line max-len
  109. 'theme_advanced_buttons4': 'insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,pagebreak', //eslint-disable-line max-len
  110. 'theme_advanced_toolbar_location': 'top',
  111. 'theme_advanced_toolbar_align': 'left',
  112. 'theme_advanced_statusbar_location': 'bottom',
  113. 'valid_elements': this.schema.validElements.join(','),
  114. 'valid_children': this.schema.validChildren.join(','),
  115. 'theme_advanced_resizing': true,
  116. 'theme_advanced_resize_horizontal': false,
  117. 'convert_urls': false,
  118. 'relative_urls': false,
  119. 'content_css': this.config['content_css'],
  120. 'custom_popup_css': this.config['popup_css'],
  121. 'magentowidget_url': this.config['widget_window_url'],
  122. 'noneditable_leave_contenteditable': true,
  123. magentoPluginsOptions: magentoPluginsOptions,
  124. doctype: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">', //eslint-disable-line max-len
  125. /**
  126. * @param {Object} ed
  127. */
  128. setup: function (ed) {
  129. var onChange;
  130. ed.onPreInit.add(self.onEditorPreInit.bind(self));
  131. ed.onInit.add(self.onEditorInit.bind(self));
  132. ed.onInit.add(function (editor) {
  133. varienGlobalEvents.fireEvent('wysiwygEditorInitialized', editor);
  134. });
  135. ed.onSubmit.add(function (edi, e) {
  136. varienGlobalEvents.fireEvent('tinymceSubmit', e);
  137. });
  138. ed.onPaste.add(function (edi, e, o) {
  139. varienGlobalEvents.fireEvent('tinymcePaste', o);
  140. });
  141. ed.onBeforeSetContent.add(function (edi, o) {
  142. varienGlobalEvents.fireEvent('tinymceBeforeSetContent', o);
  143. });
  144. ed.onSetContent.add(function (edi, o) {
  145. varienGlobalEvents.fireEvent('tinymceSetContent', o);
  146. });
  147. ed.onSaveContent.add(function (edi, o) {
  148. varienGlobalEvents.fireEvent('tinymceSaveContent', o);
  149. });
  150. /**
  151. * @param {*} edi
  152. * @param {*} l
  153. */
  154. onChange = function (edi, l) {
  155. varienGlobalEvents.fireEvent('tinymceChange', l);
  156. };
  157. ed.onChange.add(onChange);
  158. ed.onKeyUp.add(onChange);
  159. ed.onExecCommand.add(function (edi, cmd) {
  160. varienGlobalEvents.fireEvent('tinymceExecCommand', cmd);
  161. });
  162. }
  163. };
  164. // jscs:disable requireDotNotation
  165. if (!settings['style_formats']) {
  166. settings['theme_advanced_buttons1'] = settings['theme_advanced_buttons1'].replace(',styleselect', '');
  167. }
  168. // Set the document base URL
  169. if (this.config['document_base_url']) {
  170. settings['document_base_url'] = this.config['document_base_url'];
  171. }
  172. if (this.config['files_browser_window_url']) {
  173. /**
  174. * @param {*} fieldName
  175. * @param {*} url
  176. * @param {*} objectType
  177. * @param {*} w
  178. */
  179. settings['file_browser_callback'] = function (fieldName, url, objectType, w) {
  180. varienGlobalEvents.fireEvent('open_browser_callback', {
  181. win: w,
  182. type: objectType,
  183. field: fieldName
  184. });
  185. };
  186. }
  187. // jscs:enable requireDotNotation
  188. if (this.config.width) {
  189. settings.width = this.config.width;
  190. }
  191. if (this.config.height) {
  192. settings.height = this.config.height;
  193. }
  194. if (this.config.settings) {
  195. Object.extend(settings, this.config.settings);
  196. }
  197. return settings;
  198. },
  199. /**
  200. * @param {Object} editor
  201. */
  202. applySchema: function (editor) {
  203. var schema = editor.schema,
  204. schemaData = this.schema,
  205. makeMap = tinyMCE3.makeMap;
  206. jQuery.extend(true, {
  207. nonEmpty: schema.getNonEmptyElements(),
  208. boolAttrs: schema.getBoolAttrs(),
  209. whiteSpace: schema.getWhiteSpaceElements(),
  210. shortEnded: schema.getShortEndedElements(),
  211. selfClosing: schema.getSelfClosingElements(),
  212. blockElements: schema.getBlockElements()
  213. }, {
  214. nonEmpty: makeMap(schemaData.nonEmpty),
  215. boolAttrs: makeMap(schemaData.boolAttrs),
  216. whiteSpace: makeMap(schemaData.whiteSpace),
  217. shortEnded: makeMap(schemaData.shortEnded),
  218. selfClosing: makeMap(schemaData.selfClosing),
  219. blockElements: makeMap(schemaData.blockElements)
  220. });
  221. },
  222. /**
  223. * @param {String} id
  224. */
  225. get: function (id) {
  226. return tinyMCE3.get(id);
  227. },
  228. /**
  229. * @return {String|null}
  230. */
  231. getId: function () {
  232. return this.id || (this.activeEditor() ? this.activeEditor().id : null) || tinyMceEditors.values()[0].id;
  233. },
  234. /**
  235. * @return {Object}
  236. */
  237. activeEditor: function () {
  238. return tinyMCE3.activeEditor;
  239. },
  240. /**
  241. * Insert content to active editor.
  242. *
  243. * @param {String} content
  244. * @param {Boolean} ui
  245. */
  246. insertContent: function (content, ui) {
  247. this.activeEditor().execCommand('mceInsertContent', typeof ui !== 'undefined' ? ui : false, content);
  248. },
  249. /**
  250. * Set the status of the toolbar to disabled or enabled (true for enabled, false for disabled)
  251. * @param {Boolean} enabled
  252. */
  253. setToolbarStatus: function (enabled) {
  254. _.each(this.activeEditor().controlManager.controls, function (property, index, controls) {
  255. controls[property.id].setDisabled(!enabled);
  256. });
  257. },
  258. /**
  259. * Set the status of the editor and toolbar
  260. *
  261. * @param {Boolean} enabled
  262. */
  263. setEnabledStatus: function (enabled) {
  264. if (this.activeEditor()) {
  265. this.activeEditor().getBody().setAttribute('contenteditable', enabled);
  266. this.activeEditor().readonly = !enabled;
  267. this.setToolbarStatus(enabled);
  268. }
  269. if (enabled) {
  270. this.getTextArea().removeProp('disabled');
  271. } else {
  272. this.getTextArea().prop('disabled', 'disabled');
  273. }
  274. },
  275. /**
  276. * Set caret location in WYSIWYG editor.
  277. *
  278. * @param {Object} targetElement
  279. */
  280. setCaretOnElement: function (targetElement) {
  281. this.activeEditor().selection.select(targetElement);
  282. this.activeEditor().selection.collapse();
  283. },
  284. /**
  285. * @param {Object} o
  286. */
  287. openFileBrowser: function (o) {
  288. var typeTitle = this.translate('Select Images'),
  289. storeId = this.config['store_id'] !== null ? this.config['store_id'] : 0,
  290. frameDialog = jQuery(o.win.frameElement).parents('[role="dialog"]'),
  291. wUrl = this.config['files_browser_window_url'] +
  292. 'target_element_id/' + this.getId() + '/' +
  293. 'store/' + storeId + '/';
  294. this.mediaBrowserOpener = o.win;
  295. this.mediaBrowserTargetElementId = o.field;
  296. if (typeof o.type != 'undefined' && o.type != '') { //eslint-disable-line eqeqeq
  297. wUrl = wUrl + 'type/' + o.type + '/';
  298. }
  299. frameDialog.hide();
  300. jQuery('#mceModalBlocker').hide();
  301. require(['mage/adminhtml/browser'], function () {
  302. MediabrowserUtility.openDialog(wUrl, false, false, typeTitle, {
  303. /**
  304. * Closed.
  305. */
  306. closed: function () {
  307. frameDialog.show();
  308. jQuery('#mceModalBlocker').show();
  309. }
  310. });
  311. });
  312. },
  313. /**
  314. * @param {String} string
  315. * @return {String}
  316. */
  317. translate: function (string) {
  318. return jQuery.mage.__ ? jQuery.mage.__(string) : string;
  319. },
  320. /**
  321. * @return {null}
  322. */
  323. getMediaBrowserOpener: function () {
  324. return this.mediaBrowserOpener;
  325. },
  326. /**
  327. * @return {null}
  328. */
  329. getMediaBrowserTargetElementId: function () {
  330. return this.mediaBrowserTargetElementId;
  331. },
  332. /**
  333. * @return {jQuery|*|HTMLElement}
  334. */
  335. getToggleButton: function () {
  336. return $('toggle' + this.getId());
  337. },
  338. /**
  339. * Get plugins button.
  340. */
  341. getPluginButtons: function () {
  342. return jQuery('#buttons' + this.getId() + ' > button.plugin');
  343. },
  344. /**
  345. * @param {*} mode
  346. * @return {wysiwygSetup}
  347. */
  348. turnOn: function (mode) {
  349. this.closePopups();
  350. this.setup(mode);
  351. tinyMCE3.execCommand('mceAddControl', false, this.getId());
  352. this.getPluginButtons().hide();
  353. return this;
  354. },
  355. /**
  356. * @return {wysiwygSetup}
  357. */
  358. turnOff: function () {
  359. this.closePopups();
  360. tinyMCE3.execCommand('mceRemoveControl', false, this.getId());
  361. this.getPluginButtons().show();
  362. return this;
  363. },
  364. /**
  365. * Close popups.
  366. */
  367. closePopups: function () {
  368. if (typeof closeEditorPopup == 'function') {
  369. // close all popups to avoid problems with updating parent content area
  370. closeEditorPopup('widget_window' + this.getId());
  371. closeEditorPopup('browser_window' + this.getId());
  372. }
  373. },
  374. /**
  375. * @return {Boolean}
  376. */
  377. toggle: function () {
  378. if (!tinyMCE3.get(this.getId())) {
  379. this.turnOn();
  380. return true;
  381. }
  382. this.turnOff();
  383. return false;
  384. },
  385. /**
  386. * Editor pre-initialise event handler.
  387. */
  388. onEditorPreInit: function (editor) {
  389. this.applySchema(editor);
  390. },
  391. /**
  392. * @deprecated
  393. */
  394. onEditorInit: function () {},
  395. /**
  396. * On form validation.
  397. */
  398. onFormValidation: function () {
  399. if (tinyMCE3.get(this.getId())) {
  400. $(this.getId()).value = tinyMCE3.get(this.getId()).getContent();
  401. }
  402. },
  403. /**
  404. * On change content.
  405. */
  406. onChangeContent: function () {
  407. // Add "changed" to tab class if it exists
  408. var tab;
  409. this.updateTextArea();
  410. if (this.config['tab_id']) {
  411. tab = $$('a[id$=' + this.config['tab_id'] + ']')[0];
  412. if ($(tab) != undefined && $(tab).hasClassName('tab-item-link')) { //eslint-disable-line eqeqeq
  413. $(tab).addClassName('changed');
  414. }
  415. }
  416. },
  417. /**
  418. * Retrieve directives URL with substituted directive value.
  419. *
  420. * @param {String} directive
  421. */
  422. makeDirectiveUrl: function (directive) {
  423. return this.config['directives_url']
  424. .replace(/directive/, 'directive/___directive/' + directive)
  425. .replace(/\/$/, '');
  426. },
  427. /**
  428. * Convert {{directive}} style attributes syntax to absolute URLs
  429. * @param {Object} content
  430. * @return {*}
  431. */
  432. encodeDirectives: function (content) {
  433. // collect all HTML tags with attributes that contain directives
  434. return content.gsub(/<([a-z0-9\-\_]+[^>]+?)([a-z0-9\-\_]+="[^"]*?\{\{.+?\}\}.*?".*?)>/i, function (match) {
  435. var attributesString = match[2],
  436. decodedDirectiveString;
  437. // process tag attributes string
  438. attributesString = attributesString.gsub(/([a-z0-9\-\_]+)="(.*?)(\{\{.+?\}\})(.*?)"/i, function (m) {
  439. decodedDirectiveString = encodeURIComponent(Base64.mageEncode(m[3].replace(/&quot;/g, '"')));
  440. return m[1] + '="' + m[2] + this.makeDirectiveUrl(decodedDirectiveString) + m[4] + '"';
  441. }.bind(this));
  442. return '<' + match[1] + attributesString + '>';
  443. }.bind(this));
  444. },
  445. /**
  446. * @param {Object} content
  447. * @return {*}
  448. */
  449. encodeWidgets: function (content) {
  450. return content.gsub(/\{\{widget(.*?)\}\}/i, function (match) {
  451. var attributes = this.parseAttributesString(match[1]),
  452. imageSrc,
  453. imageHtml;
  454. if (attributes.type) {
  455. attributes.type = attributes.type.replace(/\\\\/g, '\\');
  456. imageSrc = this.config['widget_placeholders'][attributes.type];
  457. imageHtml = '<img';
  458. imageHtml += ' id="' + Base64.idEncode(match[0]) + '"';
  459. imageHtml += ' src="' + imageSrc + '"';
  460. imageHtml += ' title="' +
  461. match[0].replace(/\{\{/g, '{').replace(/\}\}/g, '}').replace(/\"/g, '&quot;') + '"';
  462. imageHtml += '>';
  463. return imageHtml;
  464. }
  465. }.bind(this));
  466. },
  467. /**
  468. * Convert absolute URLs to {{directive}} style attributes syntax
  469. * @param {Object} content
  470. * @return {*}
  471. */
  472. decodeDirectives: function (content) {
  473. var directiveUrl = this.makeDirectiveUrl('%directive%').split('?')[0], // remove query string from directive
  474. // escape special chars in directives url to use in regular expression
  475. regexEscapedDirectiveUrl = directiveUrl.replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'),
  476. regexDirectiveUrl = regexEscapedDirectiveUrl
  477. .replace(
  478. '%directive%',
  479. '([a-zA-Z0-9,_-]+(?:%2[A-Z]|)+\/?)(?:(?!").)*'
  480. ) + '/?(\\\\?[^"]*)?', // allow optional query string
  481. reg = new RegExp(regexDirectiveUrl);
  482. return content.gsub(reg, function (match) {
  483. return Base64.mageDecode(decodeURIComponent(match[1]).replace(/\/$/, '')).replace(/"/g, '&quot;');
  484. });
  485. },
  486. /**
  487. * @param {Object} content
  488. * @return {*}
  489. */
  490. decodeWidgets: function (content) {
  491. return content.gsub(/<img([^>]+id=\"[^>]+)>/i, function (match) {
  492. var attributes = this.parseAttributesString(match[1]),
  493. widgetCode;
  494. if (attributes.id) {
  495. widgetCode = Base64.idDecode(attributes.id);
  496. if (widgetCode.indexOf('{{widget') !== -1) {
  497. return widgetCode;
  498. }
  499. }
  500. return match[0];
  501. }.bind(this));
  502. },
  503. /**
  504. * @param {Object} attributes
  505. * @return {Object}
  506. */
  507. parseAttributesString: function (attributes) {
  508. var result = {};
  509. attributes.gsub(
  510. /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/,
  511. function (match) {
  512. result[match[1]] = match[2];
  513. }
  514. );
  515. return result;
  516. },
  517. /**
  518. * Update text area.
  519. */
  520. updateTextArea: function () {
  521. var editor = tinyMCE3.get(this.getId()),
  522. content;
  523. if (!editor) {
  524. return;
  525. }
  526. content = editor.getContent();
  527. content = this.decodeContent(content);
  528. this.getTextArea().val(content).trigger('change');
  529. },
  530. /**
  531. * @return {Object} jQuery textarea element
  532. */
  533. getTextArea: function () {
  534. return jQuery('#' + this.getId());
  535. },
  536. /**
  537. * @param {Object} content
  538. * @return {*}
  539. */
  540. decodeContent: function (content) {
  541. var result = content;
  542. if (this.config['add_widgets']) {
  543. result = this.decodeWidgets(result);
  544. result = this.decodeDirectives(result);
  545. } else if (this.config['add_directives']) {
  546. result = this.decodeDirectives(result);
  547. }
  548. return result;
  549. },
  550. /**
  551. * @param {Object} content
  552. * @return {*}
  553. */
  554. encodeContent: function (content) {
  555. var result = content;
  556. if (this.config['add_widgets']) {
  557. result = this.encodeWidgets(this.decodeWidgets(result));
  558. result = this.encodeDirectives(result);
  559. } else if (this.config['add_directives']) {
  560. result = this.encodeDirectives(result);
  561. }
  562. return result;
  563. },
  564. /**
  565. * @param {Object} o
  566. */
  567. beforeSetContent: function (o) {
  568. o.content = this.encodeContent(o.content);
  569. },
  570. /**
  571. * @param {Object} o
  572. */
  573. saveContent: function (o) {
  574. o.content = this.decodeContent(o.content);
  575. },
  576. /**
  577. * @returns {Object}
  578. */
  579. getAdapterPrototype: function () {
  580. return tinyMce3Wysiwyg;
  581. }
  582. };
  583. return tinyMce3Wysiwyg.prototype;
  584. });