ckeditor-3.0.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. (function($) {
  2. CKEDITOR.disableAutoInline = true;
  3. // Exclude every id starting with 'cke_' in ajax_html_ids during AJAX requests.
  4. Drupal.wysiwyg.excludeIdSelectors.wysiwyg_ckeditor = ['[id^="cke_"]'];
  5. // Keeps track of private instance data.
  6. var instanceMap;
  7. /**
  8. * Initialize the editor library.
  9. *
  10. * This method is called once the first time a library is needed. If new
  11. * WYSIWYG fieldsare added later, update() will be called instead.
  12. *
  13. * @param settings
  14. * An object containing editor settings for each input format.
  15. * @param pluginInfo
  16. * An object containing global plugin configuration.
  17. */
  18. Drupal.wysiwyg.editor.init.ckeditor = function(settings, pluginInfo) {
  19. instanceMap = {};
  20. // Nothing to do here other than register new plugins etc.
  21. Drupal.wysiwyg.editor.update.ckeditor(settings, pluginInfo);
  22. };
  23. /**
  24. * Update the editor library when new settings are available.
  25. *
  26. * This method is called instead of init() when at least one new WYSIWYG field
  27. * has been added to the document and the library has already been initialized.
  28. *
  29. * $param settings
  30. * An object containing editor settings for each input format.
  31. * $param pluginInfo
  32. * An object containing global plugin configuration.
  33. */
  34. Drupal.wysiwyg.editor.update.ckeditor = function(settings, pluginInfo) {
  35. // Register native external plugins.
  36. // Array syntax required; 'native' is a predefined token in JavaScript.
  37. for (var pluginId in pluginInfo['native']) {
  38. if (pluginInfo['native'].hasOwnProperty(pluginId) && (!CKEDITOR.plugins.externals || !CKEDITOR.plugins.externals[pluginId])) {
  39. var plugin = pluginInfo['native'][pluginId];
  40. CKEDITOR.plugins.addExternal(pluginId, plugin.path, plugin.fileName);
  41. }
  42. }
  43. // Build and register Drupal plugin wrappers.
  44. for (var pluginId in pluginInfo.drupal) {
  45. if (pluginInfo.drupal.hasOwnProperty(pluginId) && (!CKEDITOR.plugins.registered || !CKEDITOR.plugins.registered[pluginId])) {
  46. Drupal.wysiwyg.editor.instance.ckeditor.addPlugin(pluginId, pluginInfo.drupal[pluginId]);
  47. }
  48. }
  49. // Register Font styles (versions 3.2.1 and above).
  50. for (var format in settings) {
  51. if (settings[format].stylesSet && (!CKEDITOR.stylesSet || !CKEDITOR.stylesSet.registered[format])) {
  52. CKEDITOR.stylesSet.add(format, settings[format].stylesSet);
  53. }
  54. }
  55. };
  56. /**
  57. * Attach this editor to a target element.
  58. */
  59. Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) {
  60. // Apply editor instance settings.
  61. CKEDITOR.config.customConfig = '';
  62. var $drupalToolbars = $('#toolbar, #admin-menu', Drupal.overlayChild ? window.parent.document : document);
  63. if (!settings.height) {
  64. settings.height = $('#' + params.field).height();
  65. }
  66. settings.on = {
  67. instanceReady: function(ev) {
  68. var editor = ev.editor;
  69. // Get a list of block, list and table tags from CKEditor's XHTML DTD.
  70. // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Output_Formatting.
  71. var dtd = CKEDITOR.dtd;
  72. var tags = CKEDITOR.tools.extend({}, dtd.$block, dtd.$listItem, dtd.$tableContent);
  73. // Set source formatting rules for each listed tag except <pre>.
  74. // Linebreaks can be inserted before or after opening and closing tags.
  75. if (settings.simple_source_formatting) {
  76. // Mimic FCKeditor output, by breaking lines between tags.
  77. for (var tag in tags) {
  78. if (tag == 'pre') {
  79. continue;
  80. }
  81. this.dataProcessor.writer.setRules(tag, {
  82. indent: true,
  83. breakBeforeOpen: true,
  84. breakAfterOpen: false,
  85. breakBeforeClose: false,
  86. breakAfterClose: true
  87. });
  88. }
  89. }
  90. else {
  91. // CKEditor adds default formatting to <br>, so we want to remove that
  92. // here too.
  93. tags.br = 1;
  94. // No indents or linebreaks;
  95. for (var tag in tags) {
  96. if (tag == 'pre') {
  97. continue;
  98. }
  99. this.dataProcessor.writer.setRules(tag, {
  100. indent: false,
  101. breakBeforeOpen: false,
  102. breakAfterOpen: false,
  103. breakBeforeClose: false,
  104. breakAfterClose: false
  105. });
  106. }
  107. }
  108. },
  109. pluginsLoaded: function(ev) {
  110. var wysiwygInstance = instanceMap[this.name];
  111. var enabledPlugins = wysiwygInstance.pluginInfo.instances.drupal;
  112. // Override the conversion methods to let Drupal plugins modify the data.
  113. var editor = ev.editor;
  114. if (editor.dataProcessor && enabledPlugins) {
  115. editor.dataProcessor.toHtml = CKEDITOR.tools.override(editor.dataProcessor.toHtml, function(originalToHtml) {
  116. // Convert raw data for display in WYSIWYG mode.
  117. return function(data, fixForBody) {
  118. for (var plugin in enabledPlugins) {
  119. if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
  120. data = Drupal.wysiwyg.plugins[plugin].attach(data, wysiwygInstance.pluginInfo.global.drupal[plugin], editor.name);
  121. data = wysiwygInstance.prepareContent(data);
  122. }
  123. }
  124. return originalToHtml.call(this, data, fixForBody);
  125. };
  126. });
  127. editor.dataProcessor.toDataFormat = CKEDITOR.tools.override(editor.dataProcessor.toDataFormat, function(originalToDataFormat) {
  128. // Convert WYSIWYG mode content to raw data.
  129. return function(data, fixForBody) {
  130. data = originalToDataFormat.call(this, data, fixForBody);
  131. for (var plugin in enabledPlugins) {
  132. if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
  133. data = Drupal.wysiwyg.plugins[plugin].detach(data, wysiwygInstance.pluginInfo.global.drupal[plugin], editor.name);
  134. }
  135. }
  136. return data;
  137. };
  138. });
  139. }
  140. },
  141. selectionChange: function (event) {
  142. var wysiwygInstance = instanceMap[this.name];
  143. var enabledPlugins = wysiwygInstance.pluginInfo.instances.drupal;
  144. for (var name in enabledPlugins) {
  145. var plugin = Drupal.wysiwyg.plugins[name];
  146. if ($.isFunction(plugin.isNode)) {
  147. var node = event.data.selection.getSelectedElement();
  148. var state = plugin.isNode(node ? node.$ : null) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
  149. event.editor.getCommand(name).setState(state);
  150. }
  151. }
  152. },
  153. focus: function(ev) {
  154. Drupal.wysiwyg.activeId = ev.editor.name;
  155. },
  156. afterCommandExec: function(ev) {
  157. // Fix Drupal toolbar obscuring editor toolbar in fullscreen mode.
  158. if (ev.data.name != 'maximize') {
  159. return;
  160. }
  161. if (ev.data.command.state == CKEDITOR.TRISTATE_ON) {
  162. $drupalToolbars.hide();
  163. }
  164. else {
  165. $drupalToolbars.show();
  166. }
  167. },
  168. destroy: function (event) {
  169. // Free our reference to the private instance to not risk memory leaks.
  170. delete instanceMap[this.name];
  171. }
  172. };
  173. instanceMap[params.field] = this;
  174. // Attach editor.
  175. var editorInstance = CKEDITOR.replace(params.field, settings);
  176. };
  177. /**
  178. * Detach a single editor instance.
  179. */
  180. Drupal.wysiwyg.editor.detach.ckeditor = function (context, params, trigger) {
  181. var method = (trigger == 'serialize') ? 'updateElement' : 'destroy';
  182. var instance = CKEDITOR.instances[params.field];
  183. if (!instance) {
  184. return;
  185. }
  186. instance[method]();
  187. };
  188. Drupal.wysiwyg.editor.instance.ckeditor = {
  189. addPlugin: function (pluginName, pluginSettings) {
  190. CKEDITOR.plugins.add(pluginName, {
  191. // Wrap Drupal plugin in a proxy pluygin.
  192. init: function(editor) {
  193. if (pluginSettings.css) {
  194. editor.on('mode', function(ev) {
  195. if (ev.editor.mode == 'wysiwyg') {
  196. // Inject CSS files directly into the editing area head tag.
  197. var iframe = $('#cke_contents_' + ev.editor.name + ' iframe, #' + ev.editor.id + '_contents iframe');
  198. $('head', iframe.eq(0).contents()).append('<link rel="stylesheet" href="' + pluginSettings.css + '" type="text/css" >');
  199. }
  200. });
  201. }
  202. if (typeof Drupal.wysiwyg.plugins[pluginName].invoke == 'function') {
  203. var pluginCommand = {
  204. exec: function (editor) {
  205. var data = { format: 'html', node: null, content: '' };
  206. var selection = editor.getSelection();
  207. if (selection) {
  208. data.node = selection.getSelectedElement();
  209. if (data.node) {
  210. data.node = data.node.$;
  211. }
  212. if (selection.getType() == CKEDITOR.SELECTION_TEXT) {
  213. data.content = selection.getSelectedText();
  214. }
  215. else if (data.node) {
  216. // content is supposed to contain the "outerHTML".
  217. data.content = data.node.parentNode.innerHTML;
  218. }
  219. }
  220. Drupal.wysiwyg.plugins[pluginName].invoke(data, pluginSettings, editor.name);
  221. }
  222. };
  223. editor.addCommand(pluginName, pluginCommand);
  224. }
  225. editor.ui.addButton(pluginName, {
  226. label: pluginSettings.title,
  227. command: pluginName,
  228. icon: pluginSettings.icon
  229. });
  230. // @todo Add button state handling.
  231. }
  232. });
  233. },
  234. prepareContent: function(content) {
  235. // @todo Don't know if we need this yet.
  236. return content;
  237. },
  238. insert: function(content) {
  239. content = this.prepareContent(content);
  240. if (CKEDITOR.env.webkit || CKEDITOR.env.chrome || CKEDITOR.env.opera || CKEDITOR.env.safari) {
  241. // Works around a WebKit bug which removes wrapper elements.
  242. // @see https://drupal.org/node/1927968
  243. var tmp = new CKEDITOR.dom.element('div'), children, skip = 0, item;
  244. tmp.setHtml(content);
  245. children = tmp.getChildren();
  246. skip = 0;
  247. while (children.count() > skip) {
  248. item = children.getItem(skip);
  249. switch(item.type) {
  250. case 1:
  251. CKEDITOR.instances[this.field].insertElement(item);
  252. break;
  253. case 3:
  254. CKEDITOR.instances[this.field].insertText(item.getText());
  255. skip++;
  256. break;
  257. case 8:
  258. CKEDITOR.instances[this.field].insertHtml(item.getOuterHtml());
  259. skip++;
  260. break;
  261. }
  262. }
  263. }
  264. else {
  265. CKEDITOR.instances[this.field].insertHtml(content);
  266. }
  267. },
  268. setContent: function (content) {
  269. CKEDITOR.instances[this.field].setData(content);
  270. },
  271. getContent: function () {
  272. return CKEDITOR.instances[this.field].getData();
  273. },
  274. isFullscreen: function () {
  275. var cmd = CKEDITOR.instances[this.field].commands.maximize;
  276. return !!(cmd && cmd.state == CKEDITOR.TRISTATE_ON);
  277. }
  278. };
  279. })(jQuery);