Editor.js 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181
  1. /**
  2. * Editor.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /*jshint scripturl:true */
  11. /**
  12. * Include the base event class documentation.
  13. *
  14. * @include ../../../tools/docs/tinymce.Event.js
  15. */
  16. /**
  17. * This class contains the core logic for a TinyMCE editor.
  18. *
  19. * @class tinymce.Editor
  20. * @mixes tinymce.util.Observable
  21. * @example
  22. * // Add a class to all paragraphs in the editor.
  23. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  24. *
  25. * // Gets the current editors selection as text
  26. * tinymce.activeEditor.selection.getContent({format: 'text'});
  27. *
  28. * // Creates a new editor instance
  29. * var ed = new tinymce.Editor('textareaid', {
  30. * some_setting: 1
  31. * }, tinymce.EditorManager);
  32. *
  33. * // Select each item the user clicks on
  34. * ed.on('click', function(e) {
  35. * ed.selection.select(e.target);
  36. * });
  37. *
  38. * ed.render();
  39. */
  40. define("tinymce/Editor", [
  41. "tinymce/dom/DOMUtils",
  42. "tinymce/AddOnManager",
  43. "tinymce/html/Node",
  44. "tinymce/dom/Serializer",
  45. "tinymce/html/Serializer",
  46. "tinymce/dom/Selection",
  47. "tinymce/Formatter",
  48. "tinymce/UndoManager",
  49. "tinymce/EnterKey",
  50. "tinymce/ForceBlocks",
  51. "tinymce/EditorCommands",
  52. "tinymce/util/URI",
  53. "tinymce/dom/ScriptLoader",
  54. "tinymce/dom/EventUtils",
  55. "tinymce/WindowManager",
  56. "tinymce/html/Schema",
  57. "tinymce/html/DomParser",
  58. "tinymce/util/Quirks",
  59. "tinymce/Env",
  60. "tinymce/util/Tools",
  61. "tinymce/util/Observable",
  62. "tinymce/Shortcuts"
  63. ], function(
  64. DOMUtils, AddOnManager, Node, DomSerializer, Serializer,
  65. Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
  66. URI, ScriptLoader, EventUtils, WindowManager,
  67. Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts
  68. ) {
  69. // Shorten these names
  70. var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
  71. var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
  72. var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
  73. var Event = EventUtils.Event;
  74. var isGecko = Env.gecko, ie = Env.ie;
  75. function getEventTarget(editor, eventName) {
  76. if (eventName == 'selectionchange') {
  77. return editor.getDoc();
  78. }
  79. // Need to bind mousedown/mouseup etc to document not body in iframe mode
  80. // Since the user might click on the HTML element not the BODY
  81. if (!editor.inline && /^mouse|click|contextmenu|drop/.test(eventName)) {
  82. return editor.getDoc();
  83. }
  84. return editor.getBody();
  85. }
  86. /**
  87. * Include documentation for all the events.
  88. *
  89. * @include ../../../tools/docs/tinymce.Editor.js
  90. */
  91. /**
  92. * Constructs a editor instance by id.
  93. *
  94. * @constructor
  95. * @method Editor
  96. * @param {String} id Unique id for the editor.
  97. * @param {Object} settings Settings for the editor.
  98. * @param {tinymce.EditorManager} editorManager EditorManager instance.
  99. * @author Moxiecode
  100. */
  101. function Editor(id, settings, editorManager) {
  102. var self = this, documentBaseUrl, baseUri;
  103. documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
  104. baseUri = editorManager.baseURI;
  105. /**
  106. * Name/value collection with editor settings.
  107. *
  108. * @property settings
  109. * @type Object
  110. * @example
  111. * // Get the value of the theme setting
  112. * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
  113. */
  114. self.settings = settings = extend({
  115. id: id,
  116. theme: 'modern',
  117. delta_width: 0,
  118. delta_height: 0,
  119. popup_css: '',
  120. plugins: '',
  121. document_base_url: documentBaseUrl,
  122. add_form_submit_trigger: true,
  123. submit_patch: true,
  124. add_unload_trigger: true,
  125. convert_urls: true,
  126. relative_urls: true,
  127. remove_script_host: true,
  128. object_resizing: true,
  129. doctype: '<!DOCTYPE html>',
  130. visual: true,
  131. font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
  132. // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
  133. font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
  134. forced_root_block: 'p',
  135. hidden_input: true,
  136. padd_empty_editor: true,
  137. render_ui: true,
  138. indentation: '30px',
  139. inline_styles: true,
  140. convert_fonts_to_spans: true,
  141. indent: 'simple',
  142. indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
  143. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  144. indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
  145. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  146. validate: true,
  147. entity_encoding: 'named',
  148. url_converter: self.convertURL,
  149. url_converter_scope: self,
  150. ie7_compat: true
  151. }, settings);
  152. AddOnManager.language = settings.language || 'en';
  153. AddOnManager.languageLoad = settings.language_load;
  154. AddOnManager.baseURL = editorManager.baseURL;
  155. /**
  156. * Editor instance id, normally the same as the div/textarea that was replaced.
  157. *
  158. * @property id
  159. * @type String
  160. */
  161. self.id = settings.id = id;
  162. /**
  163. * State to force the editor to return false on a isDirty call.
  164. *
  165. * @property isNotDirty
  166. * @type Boolean
  167. * @example
  168. * function ajaxSave() {
  169. * var ed = tinymce.get('elm1');
  170. *
  171. * // Save contents using some XHR call
  172. * alert(ed.getContent());
  173. *
  174. * ed.isNotDirty = true; // Force not dirty state
  175. * }
  176. */
  177. self.isNotDirty = true;
  178. /**
  179. * Name/Value object containting plugin instances.
  180. *
  181. * @property plugins
  182. * @type Object
  183. * @example
  184. * // Execute a method inside a plugin directly
  185. * tinymce.activeEditor.plugins.someplugin.someMethod();
  186. */
  187. self.plugins = {};
  188. /**
  189. * URI object to document configured for the TinyMCE instance.
  190. *
  191. * @property documentBaseURI
  192. * @type tinymce.util.URI
  193. * @example
  194. * // Get relative URL from the location of document_base_url
  195. * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
  196. *
  197. * // Get absolute URL from the location of document_base_url
  198. * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
  199. */
  200. self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
  201. base_uri: baseUri
  202. });
  203. /**
  204. * URI object to current document that holds the TinyMCE editor instance.
  205. *
  206. * @property baseURI
  207. * @type tinymce.util.URI
  208. * @example
  209. * // Get relative URL from the location of the API
  210. * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
  211. *
  212. * // Get absolute URL from the location of the API
  213. * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
  214. */
  215. self.baseURI = baseUri;
  216. /**
  217. * Array with CSS files to load into the iframe.
  218. *
  219. * @property contentCSS
  220. * @type Array
  221. */
  222. self.contentCSS = [];
  223. /**
  224. * Array of CSS styles to add to head of document when the editor loads.
  225. *
  226. * @property contentStyles
  227. * @type Array
  228. */
  229. self.contentStyles = [];
  230. // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
  231. self.shortcuts = new Shortcuts(self);
  232. // Internal command handler objects
  233. self.execCommands = {};
  234. self.queryStateCommands = {};
  235. self.queryValueCommands = {};
  236. self.loadedCSS = {};
  237. self.suffix = editorManager.suffix;
  238. self.editorManager = editorManager;
  239. self.inline = settings.inline;
  240. // Call setup
  241. editorManager.fire('SetupEditor', self);
  242. self.execCallback('setup', self);
  243. }
  244. Editor.prototype = {
  245. /**
  246. * Renderes the editor/adds it to the page.
  247. *
  248. * @method render
  249. */
  250. render: function() {
  251. var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
  252. function readyHandler() {
  253. DOM.unbind(window, 'ready', readyHandler);
  254. self.render();
  255. }
  256. // Page is not loaded yet, wait for it
  257. if (!Event.domLoaded) {
  258. DOM.bind(window, 'ready', readyHandler);
  259. return;
  260. }
  261. // Element not found, then skip initialization
  262. if (!self.getElement()) {
  263. return;
  264. }
  265. // No editable support old iOS versions etc
  266. if (!Env.contentEditable) {
  267. return;
  268. }
  269. // Hide target element early to prevent content flashing
  270. if (!settings.inline) {
  271. self.orgVisibility = self.getElement().style.visibility;
  272. self.getElement().style.visibility = 'hidden';
  273. } else {
  274. self.inline = true;
  275. }
  276. var form = self.getElement().form || DOM.getParent(id, 'form');
  277. if (form) {
  278. self.formElement = form;
  279. // Add hidden input for non input elements inside form elements
  280. if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
  281. DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
  282. self.hasHiddenInput = true;
  283. }
  284. // Pass submit/reset from form to editor instance
  285. self.formEventDelegate = function(e) {
  286. self.fire(e.type, e);
  287. };
  288. DOM.bind(form, 'submit reset', self.formEventDelegate);
  289. // Reset contents in editor when the form is reset
  290. self.on('reset', function() {
  291. self.setContent(self.startContent, {format: 'raw'});
  292. });
  293. // Check page uses id="submit" or name="submit" for it's submit button
  294. if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
  295. form._mceOldSubmit = form.submit;
  296. form.submit = function() {
  297. self.editorManager.triggerSave();
  298. self.isNotDirty = true;
  299. return form._mceOldSubmit(form);
  300. };
  301. }
  302. }
  303. /**
  304. * Window manager reference, use this to open new windows and dialogs.
  305. *
  306. * @property windowManager
  307. * @type tinymce.WindowManager
  308. * @example
  309. * // Shows an alert message
  310. * tinymce.activeEditor.windowManager.alert('Hello world!');
  311. *
  312. * // Opens a new dialog with the file.htm file and the size 320x240
  313. * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
  314. * tinymce.activeEditor.windowManager.open({
  315. * url: 'file.htm',
  316. * width: 320,
  317. * height: 240
  318. * }, {
  319. * custom_param: 1
  320. * });
  321. */
  322. self.windowManager = new WindowManager(self);
  323. if (settings.encoding == 'xml') {
  324. self.on('GetContent', function(e) {
  325. if (e.save) {
  326. e.content = DOM.encode(e.content);
  327. }
  328. });
  329. }
  330. if (settings.add_form_submit_trigger) {
  331. self.on('submit', function() {
  332. if (self.initialized) {
  333. self.save();
  334. }
  335. });
  336. }
  337. if (settings.add_unload_trigger) {
  338. self._beforeUnload = function() {
  339. if (self.initialized && !self.destroyed && !self.isHidden()) {
  340. self.save({format: 'raw', no_events: true, set_dirty: false});
  341. }
  342. };
  343. self.editorManager.on('BeforeUnload', self._beforeUnload);
  344. }
  345. // Load scripts
  346. function loadScripts() {
  347. var scriptLoader = ScriptLoader.ScriptLoader;
  348. if (settings.language && settings.language != 'en' && !settings.language_url) {
  349. settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
  350. }
  351. if (settings.language_url) {
  352. scriptLoader.add(settings.language_url);
  353. }
  354. if (settings.theme && typeof settings.theme != "function" &&
  355. settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
  356. var themeUrl = settings.theme_url;
  357. if (themeUrl) {
  358. themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
  359. } else {
  360. themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
  361. }
  362. ThemeManager.load(settings.theme, themeUrl);
  363. }
  364. if (Tools.isArray(settings.plugins)) {
  365. settings.plugins = settings.plugins.join(' ');
  366. }
  367. each(settings.external_plugins, function(url, name) {
  368. PluginManager.load(name, url);
  369. settings.plugins += ' ' + name;
  370. });
  371. each(settings.plugins.split(/[ ,]/), function(plugin) {
  372. plugin = trim(plugin);
  373. if (plugin && !PluginManager.urls[plugin]) {
  374. if (plugin.charAt(0) == '-') {
  375. plugin = plugin.substr(1, plugin.length);
  376. var dependencies = PluginManager.dependencies(plugin);
  377. each(dependencies, function(dep) {
  378. var defaultSettings = {
  379. prefix:'plugins/',
  380. resource: dep,
  381. suffix:'/plugin' + suffix + '.js'
  382. };
  383. dep = PluginManager.createUrl(defaultSettings, dep);
  384. PluginManager.load(dep.resource, dep);
  385. });
  386. } else {
  387. PluginManager.load(plugin, {
  388. prefix: 'plugins/',
  389. resource: plugin,
  390. suffix: '/plugin' + suffix + '.js'
  391. });
  392. }
  393. }
  394. });
  395. scriptLoader.loadQueue(function() {
  396. if (!self.removed) {
  397. self.init();
  398. }
  399. });
  400. }
  401. loadScripts();
  402. },
  403. /**
  404. * Initializes the editor this will be called automatically when
  405. * all plugins/themes and language packs are loaded by the rendered method.
  406. * This method will setup the iframe and create the theme and plugin instances.
  407. *
  408. * @method init
  409. */
  410. init: function() {
  411. var self = this, settings = self.settings, elm = self.getElement();
  412. var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
  413. self.rtl = this.editorManager.i18n.rtl;
  414. self.editorManager.add(self);
  415. settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
  416. /**
  417. * Reference to the theme instance that was used to generate the UI.
  418. *
  419. * @property theme
  420. * @type tinymce.Theme
  421. * @example
  422. * // Executes a method on the theme directly
  423. * tinymce.activeEditor.theme.someMethod();
  424. */
  425. if (settings.theme) {
  426. if (typeof settings.theme != "function") {
  427. settings.theme = settings.theme.replace(/-/, '');
  428. Theme = ThemeManager.get(settings.theme);
  429. self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
  430. if (self.theme.init) {
  431. self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''));
  432. }
  433. } else {
  434. self.theme = settings.theme;
  435. }
  436. }
  437. function initPlugin(plugin) {
  438. var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
  439. pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
  440. plugin = trim(plugin);
  441. if (Plugin && inArray(initializedPlugins, plugin) === -1) {
  442. each(PluginManager.dependencies(plugin), function(dep){
  443. initPlugin(dep);
  444. });
  445. pluginInstance = new Plugin(self, pluginUrl);
  446. self.plugins[plugin] = pluginInstance;
  447. if (pluginInstance.init) {
  448. pluginInstance.init(self, pluginUrl);
  449. initializedPlugins.push(plugin);
  450. }
  451. }
  452. }
  453. // Create all plugins
  454. each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
  455. // Measure box
  456. if (settings.render_ui && self.theme) {
  457. self.orgDisplay = elm.style.display;
  458. if (typeof settings.theme != "function") {
  459. w = settings.width || elm.style.width || elm.offsetWidth;
  460. h = settings.height || elm.style.height || elm.offsetHeight;
  461. minHeight = settings.min_height || 100;
  462. re = /^[0-9\.]+(|px)$/i;
  463. if (re.test('' + w)) {
  464. w = Math.max(parseInt(w, 10), 100);
  465. }
  466. if (re.test('' + h)) {
  467. h = Math.max(parseInt(h, 10), minHeight);
  468. }
  469. // Render UI
  470. o = self.theme.renderUI({
  471. targetNode: elm,
  472. width: w,
  473. height: h,
  474. deltaWidth: settings.delta_width,
  475. deltaHeight: settings.delta_height
  476. });
  477. // Resize editor
  478. if (!settings.content_editable) {
  479. DOM.setStyles(o.sizeContainer || o.editorContainer, {
  480. wi2dth: w,
  481. // TODO: Fix this
  482. h2eight: h
  483. });
  484. h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
  485. if (h < minHeight) {
  486. h = minHeight;
  487. }
  488. }
  489. } else {
  490. o = settings.theme(self, elm);
  491. // Convert element type to id:s
  492. if (o.editorContainer.nodeType) {
  493. o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
  494. }
  495. // Convert element type to id:s
  496. if (o.iframeContainer.nodeType) {
  497. o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
  498. }
  499. // Use specified iframe height or the targets offsetHeight
  500. h = o.iframeHeight || elm.offsetHeight;
  501. }
  502. self.editorContainer = o.editorContainer;
  503. }
  504. // Load specified content CSS last
  505. if (settings.content_css) {
  506. each(explode(settings.content_css), function(u) {
  507. self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
  508. });
  509. }
  510. // Load specified content CSS last
  511. if (settings.content_style) {
  512. self.contentStyles.push(settings.content_style);
  513. }
  514. // Content editable mode ends here
  515. if (settings.content_editable) {
  516. elm = n = o = null; // Fix IE leak
  517. return self.initContentBody();
  518. }
  519. self.iframeHTML = settings.doctype + '<html><head>';
  520. // We only need to override paths if we have to
  521. // IE has a bug where it remove site absolute urls to relative ones if this is specified
  522. if (settings.document_base_url != self.documentBaseUrl) {
  523. self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
  524. }
  525. // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
  526. if (!Env.caretAfter && settings.ie7_compat) {
  527. self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
  528. }
  529. self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  530. // Load the CSS by injecting them into the HTML this will reduce "flicker"
  531. for (i = 0; i < self.contentCSS.length; i++) {
  532. var cssUrl = self.contentCSS[i];
  533. self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />';
  534. self.loadedCSS[cssUrl] = true;
  535. }
  536. bodyId = settings.body_id || 'tinymce';
  537. if (bodyId.indexOf('=') != -1) {
  538. bodyId = self.getParam('body_id', '', 'hash');
  539. bodyId = bodyId[self.id] || bodyId;
  540. }
  541. bodyClass = settings.body_class || '';
  542. if (bodyClass.indexOf('=') != -1) {
  543. bodyClass = self.getParam('body_class', '', 'hash');
  544. bodyClass = bodyClass[self.id] || '';
  545. }
  546. self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" ' +
  547. 'onload="window.parent.tinymce.get(\'' + self.id + '\').fire(\'load\');"><br></body></html>';
  548. /*eslint no-script-url:0 */
  549. var domainRelaxUrl = 'javascript:(function(){' +
  550. 'document.open();document.domain="' + document.domain + '";' +
  551. 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
  552. 'document.close();ed.initContentBody(true);})()';
  553. // Domain relaxing is required since the user has messed around with document.domain
  554. if (document.domain != location.hostname) {
  555. url = domainRelaxUrl;
  556. }
  557. // Create iframe
  558. // TODO: ACC add the appropriate description on this.
  559. n = DOM.add(o.iframeContainer, 'iframe', {
  560. id: self.id + "_ifr",
  561. src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
  562. frameBorder: '0',
  563. allowTransparency: "true",
  564. title: self.editorManager.translate(
  565. "Rich Text Area. Press ALT-F9 for menu. " +
  566. "Press ALT-F10 for toolbar. Press ALT-0 for help"
  567. ),
  568. style: {
  569. width: '100%',
  570. height: h,
  571. display: 'block' // Important for Gecko to render the iframe correctly
  572. }
  573. });
  574. // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
  575. // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
  576. if (ie) {
  577. try {
  578. self.getDoc();
  579. } catch (e) {
  580. n.src = url = domainRelaxUrl;
  581. }
  582. }
  583. self.contentAreaContainer = o.iframeContainer;
  584. if (o.editorContainer) {
  585. DOM.get(o.editorContainer).style.display = self.orgDisplay;
  586. }
  587. DOM.get(self.id).style.display = 'none';
  588. DOM.setAttrib(self.id, 'aria-hidden', true);
  589. if (!url) {
  590. self.initContentBody();
  591. }
  592. elm = n = o = null; // Cleanup
  593. },
  594. /**
  595. * This method get called by the init method ones the iframe is loaded.
  596. * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
  597. *
  598. * @method initContentBody
  599. * @private
  600. */
  601. initContentBody: function(skipWrite) {
  602. var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText;
  603. // Restore visibility on target element
  604. if (!settings.inline) {
  605. self.getElement().style.visibility = self.orgVisibility;
  606. }
  607. // Setup iframe body
  608. if (!skipWrite && !settings.content_editable) {
  609. doc.open();
  610. doc.write(self.iframeHTML);
  611. doc.close();
  612. }
  613. if (settings.content_editable) {
  614. self.on('remove', function() {
  615. var bodyEl = this.getBody();
  616. DOM.removeClass(bodyEl, 'mce-content-body');
  617. DOM.removeClass(bodyEl, 'mce-edit-focus');
  618. DOM.setAttrib(bodyEl, 'contentEditable', null);
  619. });
  620. DOM.addClass(targetElm, 'mce-content-body');
  621. self.contentDocument = doc = settings.content_document || document;
  622. self.contentWindow = settings.content_window || window;
  623. self.bodyElement = targetElm;
  624. // Prevent leak in IE
  625. settings.content_document = settings.content_window = null;
  626. // TODO: Fix this
  627. settings.root_name = targetElm.nodeName.toLowerCase();
  628. }
  629. // It will not steal focus while setting contentEditable
  630. body = self.getBody();
  631. body.disabled = true;
  632. if (!settings.readonly) {
  633. if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
  634. body.style.position = 'relative';
  635. }
  636. body.contentEditable = self.getParam('content_editable_state', true);
  637. }
  638. body.disabled = false;
  639. /**
  640. * Schema instance, enables you to validate elements and it's children.
  641. *
  642. * @property schema
  643. * @type tinymce.html.Schema
  644. */
  645. self.schema = new Schema(settings);
  646. /**
  647. * DOM instance for the editor.
  648. *
  649. * @property dom
  650. * @type tinymce.dom.DOMUtils
  651. * @example
  652. * // Adds a class to all paragraphs within the editor
  653. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  654. */
  655. self.dom = new DOMUtils(doc, {
  656. keep_values: true,
  657. url_converter: self.convertURL,
  658. url_converter_scope: self,
  659. hex_colors: settings.force_hex_style_colors,
  660. class_filter: settings.class_filter,
  661. update_styles: true,
  662. root_element: settings.content_editable ? self.id : null,
  663. collect: settings.content_editable,
  664. schema: self.schema,
  665. onSetAttrib: function(e) {
  666. self.fire('SetAttrib', e);
  667. }
  668. });
  669. /**
  670. * HTML parser will be used when contents is inserted into the editor.
  671. *
  672. * @property parser
  673. * @type tinymce.html.DomParser
  674. */
  675. self.parser = new DomParser(settings, self.schema);
  676. // Convert src and href into data-mce-src, data-mce-href and data-mce-style
  677. self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
  678. var i = nodes.length, node, dom = self.dom, value, internalName;
  679. while (i--) {
  680. node = nodes[i];
  681. value = node.attr(name);
  682. internalName = 'data-mce-' + name;
  683. // Add internal attribute if we need to we don't on a refresh of the document
  684. if (!node.attributes.map[internalName]) {
  685. if (name === "style") {
  686. node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
  687. } else if (name === "tabindex") {
  688. node.attr(internalName, value);
  689. node.attr(name, null);
  690. } else {
  691. node.attr(internalName, self.convertURL(value, name, node.name));
  692. }
  693. }
  694. }
  695. });
  696. // Keep scripts from executing
  697. self.parser.addNodeFilter('script', function(nodes) {
  698. var i = nodes.length, node;
  699. while (i--) {
  700. node = nodes[i];
  701. node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
  702. }
  703. });
  704. self.parser.addNodeFilter('#cdata', function(nodes) {
  705. var i = nodes.length, node;
  706. while (i--) {
  707. node = nodes[i];
  708. node.type = 8;
  709. node.name = '#comment';
  710. node.value = '[CDATA[' + node.value + ']]';
  711. }
  712. });
  713. self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
  714. var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
  715. while (i--) {
  716. node = nodes[i];
  717. if (node.isEmpty(nonEmptyElements)) {
  718. node.empty().append(new Node('br', 1)).shortEnded = true;
  719. }
  720. }
  721. });
  722. /**
  723. * DOM serializer for the editor. Will be used when contents is extracted from the editor.
  724. *
  725. * @property serializer
  726. * @type tinymce.dom.Serializer
  727. * @example
  728. * // Serializes the first paragraph in the editor into a string
  729. * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
  730. */
  731. self.serializer = new DomSerializer(settings, self);
  732. /**
  733. * Selection instance for the editor.
  734. *
  735. * @property selection
  736. * @type tinymce.dom.Selection
  737. * @example
  738. * // Sets some contents to the current selection in the editor
  739. * tinymce.activeEditor.selection.setContent('Some contents');
  740. *
  741. * // Gets the current selection
  742. * alert(tinymce.activeEditor.selection.getContent());
  743. *
  744. * // Selects the first paragraph found
  745. * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
  746. */
  747. self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
  748. /**
  749. * Formatter instance.
  750. *
  751. * @property formatter
  752. * @type tinymce.Formatter
  753. */
  754. self.formatter = new Formatter(self);
  755. /**
  756. * Undo manager instance, responsible for handling undo levels.
  757. *
  758. * @property undoManager
  759. * @type tinymce.UndoManager
  760. * @example
  761. * // Undoes the last modification to the editor
  762. * tinymce.activeEditor.undoManager.undo();
  763. */
  764. self.undoManager = new UndoManager(self);
  765. self.forceBlocks = new ForceBlocks(self);
  766. self.enterKey = new EnterKey(self);
  767. self.editorCommands = new EditorCommands(self);
  768. self.fire('PreInit');
  769. if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
  770. doc.body.spellcheck = false; // Gecko
  771. DOM.setAttrib(body, "spellcheck", "false");
  772. }
  773. self.fire('PostRender');
  774. self.quirks = Quirks(self);
  775. if (settings.directionality) {
  776. body.dir = settings.directionality;
  777. }
  778. if (settings.nowrap) {
  779. body.style.whiteSpace = "nowrap";
  780. }
  781. if (settings.protect) {
  782. self.on('BeforeSetContent', function(e) {
  783. each(settings.protect, function(pattern) {
  784. e.content = e.content.replace(pattern, function(str) {
  785. return '<!--mce:protected ' + escape(str) + '-->';
  786. });
  787. });
  788. });
  789. }
  790. self.on('SetContent', function() {
  791. self.addVisual(self.getBody());
  792. });
  793. // Remove empty contents
  794. if (settings.padd_empty_editor) {
  795. self.on('PostProcess', function(e) {
  796. e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
  797. });
  798. }
  799. self.load({initial: true, format: 'html'});
  800. self.startContent = self.getContent({format: 'raw'});
  801. /**
  802. * Is set to true after the editor instance has been initialized
  803. *
  804. * @property initialized
  805. * @type Boolean
  806. * @example
  807. * function isEditorInitialized(editor) {
  808. * return editor && editor.initialized;
  809. * }
  810. */
  811. self.initialized = true;
  812. each(self._pendingNativeEvents, function(name) {
  813. self.dom.bind(getEventTarget(self, name), name, function(e) {
  814. self.fire(e.type, e);
  815. });
  816. });
  817. self.fire('init');
  818. self.focus(true);
  819. self.nodeChanged({initial: true});
  820. self.execCallback('init_instance_callback', self);
  821. // Add editor specific CSS styles
  822. if (self.contentStyles.length > 0) {
  823. contentCssText = '';
  824. each(self.contentStyles, function(style) {
  825. contentCssText += style + "\r\n";
  826. });
  827. self.dom.addStyle(contentCssText);
  828. }
  829. // Load specified content CSS last
  830. each(self.contentCSS, function(cssUrl) {
  831. if (!self.loadedCSS[cssUrl]) {
  832. self.dom.loadCSS(cssUrl);
  833. self.loadedCSS[cssUrl] = true;
  834. }
  835. });
  836. // Handle auto focus
  837. if (settings.auto_focus) {
  838. setTimeout(function () {
  839. var ed = self.editorManager.get(settings.auto_focus);
  840. ed.selection.select(ed.getBody(), 1);
  841. ed.selection.collapse(1);
  842. ed.getBody().focus();
  843. ed.getWin().focus();
  844. }, 100);
  845. }
  846. // Clean up references for IE
  847. targetElm = doc = body = null;
  848. },
  849. /**
  850. * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
  851. * it will also place DOM focus inside the editor.
  852. *
  853. * @method focus
  854. * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
  855. */
  856. focus: function(skip_focus) {
  857. var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
  858. var controlElm, doc = self.getDoc(), body;
  859. if (!skip_focus) {
  860. // Get selected control element
  861. rng = selection.getRng();
  862. if (rng.item) {
  863. controlElm = rng.item(0);
  864. }
  865. self._refreshContentEditable();
  866. // Focus the window iframe
  867. if (!contentEditable) {
  868. // WebKit needs this call to fire focusin event properly see #5948
  869. // But Opera pre Blink engine will produce an empty selection so skip Opera
  870. if (!Env.opera) {
  871. self.getBody().focus();
  872. }
  873. self.getWin().focus();
  874. }
  875. // Focus the body as well since it's contentEditable
  876. if (isGecko || contentEditable) {
  877. body = self.getBody();
  878. // Check for setActive since it doesn't scroll to the element
  879. if (body.setActive) {
  880. // IE 11 sometimes throws "Invalid function" then fallback to focus
  881. try {
  882. body.setActive();
  883. } catch (ex) {
  884. body.focus();
  885. }
  886. } else {
  887. body.focus();
  888. }
  889. if (contentEditable) {
  890. selection.normalize();
  891. }
  892. }
  893. // Restore selected control element
  894. // This is needed when for example an image is selected within a
  895. // layer a call to focus will then remove the control selection
  896. if (controlElm && controlElm.ownerDocument == doc) {
  897. rng = doc.body.createControlRange();
  898. rng.addElement(controlElm);
  899. rng.select();
  900. }
  901. }
  902. if (self.editorManager.activeEditor != self) {
  903. if ((oed = self.editorManager.activeEditor)) {
  904. oed.fire('deactivate', {relatedTarget: self});
  905. }
  906. self.fire('activate', {relatedTarget: oed});
  907. }
  908. self.editorManager.activeEditor = self;
  909. },
  910. /**
  911. * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
  912. * There new event model is a better way to add callback so this method might be removed in the future.
  913. *
  914. * @method execCallback
  915. * @param {String} name Name of the callback to execute.
  916. * @return {Object} Return value passed from callback function.
  917. */
  918. execCallback: function(name) {
  919. var self = this, callback = self.settings[name], scope;
  920. if (!callback) {
  921. return;
  922. }
  923. // Look through lookup
  924. if (self.callbackLookup && (scope = self.callbackLookup[name])) {
  925. callback = scope.func;
  926. scope = scope.scope;
  927. }
  928. if (typeof(callback) === 'string') {
  929. scope = callback.replace(/\.\w+$/, '');
  930. scope = scope ? resolve(scope) : 0;
  931. callback = resolve(callback);
  932. self.callbackLookup = self.callbackLookup || {};
  933. self.callbackLookup[name] = {func: callback, scope: scope};
  934. }
  935. return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
  936. },
  937. /**
  938. * Translates the specified string by replacing variables with language pack items it will also check if there is
  939. * a key mathcin the input.
  940. *
  941. * @method translate
  942. * @param {String} text String to translate by the language pack data.
  943. * @return {String} Translated string.
  944. */
  945. translate: function(text) {
  946. var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
  947. if (!text) {
  948. return '';
  949. }
  950. return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
  951. return i18n.data[lang + '.' + b] || '{#' + b + '}';
  952. });
  953. },
  954. /**
  955. * Returns a language pack item by name/key.
  956. *
  957. * @method getLang
  958. * @param {String} name Name/key to get from the language pack.
  959. * @param {String} defaultVal Optional default value to retrive.
  960. */
  961. getLang: function(name, defaultVal) {
  962. return (
  963. this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
  964. (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
  965. );
  966. },
  967. /**
  968. * Returns a configuration parameter by name.
  969. *
  970. * @method getParam
  971. * @param {String} name Configruation parameter to retrive.
  972. * @param {String} defaultVal Optional default value to return.
  973. * @param {String} type Optional type parameter.
  974. * @return {String} Configuration parameter value or default value.
  975. * @example
  976. * // Returns a specific config value from the currently active editor
  977. * var someval = tinymce.activeEditor.getParam('myvalue');
  978. *
  979. * // Returns a specific config value from a specific editor instance by id
  980. * var someval2 = tinymce.get('my_editor').getParam('myvalue');
  981. */
  982. getParam: function(name, defaultVal, type) {
  983. var value = name in this.settings ? this.settings[name] : defaultVal, output;
  984. if (type === 'hash') {
  985. output = {};
  986. if (typeof(value) === 'string') {
  987. each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
  988. value = value.split('=');
  989. if (value.length > 1) {
  990. output[trim(value[0])] = trim(value[1]);
  991. } else {
  992. output[trim(value[0])] = trim(value);
  993. }
  994. });
  995. } else {
  996. output = value;
  997. }
  998. return output;
  999. }
  1000. return value;
  1001. },
  1002. /**
  1003. * Distpaches out a onNodeChange event to all observers. This method should be called when you
  1004. * need to update the UI states or element path etc.
  1005. *
  1006. * @method nodeChanged
  1007. */
  1008. nodeChanged: function() {
  1009. var self = this, selection = self.selection, node, parents, root;
  1010. // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
  1011. if (self.initialized && !self.settings.disable_nodechange && !self.settings.readonly) {
  1012. // Get start node
  1013. root = self.getBody();
  1014. node = selection.getStart() || root;
  1015. node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
  1016. // Edge case for <p>|<img></p>
  1017. if (node.nodeName == 'IMG' && selection.isCollapsed()) {
  1018. node = node.parentNode;
  1019. }
  1020. // Get parents and add them to object
  1021. parents = [];
  1022. self.dom.getParent(node, function(node) {
  1023. if (node === root) {
  1024. return true;
  1025. }
  1026. parents.push(node);
  1027. });
  1028. self.fire('NodeChange', {element: node, parents: parents});
  1029. }
  1030. },
  1031. /**
  1032. * Adds a button that later gets created by the theme in the editors toolbars.
  1033. *
  1034. * @method addButton
  1035. * @param {String} name Button name to add.
  1036. * @param {Object} settings Settings object with title, cmd etc.
  1037. * @example
  1038. * // Adds a custom button to the editor that inserts contents when clicked
  1039. * tinymce.init({
  1040. * ...
  1041. *
  1042. * toolbar: 'example'
  1043. *
  1044. * setup: function(ed) {
  1045. * ed.addButton('example', {
  1046. * title: 'My title',
  1047. * image: '../js/tinymce/plugins/example/img/example.gif',
  1048. * onclick: function() {
  1049. * ed.insertContent('Hello world!!');
  1050. * }
  1051. * });
  1052. * }
  1053. * });
  1054. */
  1055. addButton: function(name, settings) {
  1056. var self = this;
  1057. if (settings.cmd) {
  1058. settings.onclick = function() {
  1059. self.execCommand(settings.cmd);
  1060. };
  1061. }
  1062. if (!settings.text && !settings.icon) {
  1063. settings.icon = name;
  1064. }
  1065. self.buttons = self.buttons || {};
  1066. settings.tooltip = settings.tooltip || settings.title;
  1067. self.buttons[name] = settings;
  1068. },
  1069. /**
  1070. * Adds a menu item to be used in the menus of the theme. There might be multiple instances
  1071. * of this menu item for example it might be used in the main menus of the theme but also in
  1072. * the context menu so make sure that it's self contained and supports multiple instances.
  1073. *
  1074. * @method addMenuItem
  1075. * @param {String} name Menu item name to add.
  1076. * @param {Object} settings Settings object with title, cmd etc.
  1077. * @example
  1078. * // Adds a custom menu item to the editor that inserts contents when clicked
  1079. * // The context option allows you to add the menu item to an existing default menu
  1080. * tinymce.init({
  1081. * ...
  1082. *
  1083. * setup: function(ed) {
  1084. * ed.addMenuItem('example', {
  1085. * text: 'My menu item',
  1086. * context: 'tools',
  1087. * onclick: function() {
  1088. * ed.insertContent('Hello world!!');
  1089. * }
  1090. * });
  1091. * }
  1092. * });
  1093. */
  1094. addMenuItem: function(name, settings) {
  1095. var self = this;
  1096. if (settings.cmd) {
  1097. settings.onclick = function() {
  1098. self.execCommand(settings.cmd);
  1099. };
  1100. }
  1101. self.menuItems = self.menuItems || {};
  1102. self.menuItems[name] = settings;
  1103. },
  1104. /**
  1105. * Adds a custom command to the editor, you can also override existing commands with this method.
  1106. * The command that you add can be executed with execCommand.
  1107. *
  1108. * @method addCommand
  1109. * @param {String} name Command name to add/override.
  1110. * @param {addCommandCallback} callback Function to execute when the command occurs.
  1111. * @param {Object} scope Optional scope to execute the function in.
  1112. * @example
  1113. * // Adds a custom command that later can be executed using execCommand
  1114. * tinymce.init({
  1115. * ...
  1116. *
  1117. * setup: function(ed) {
  1118. * // Register example command
  1119. * ed.addCommand('mycommand', function(ui, v) {
  1120. * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
  1121. * });
  1122. * }
  1123. * });
  1124. */
  1125. addCommand: function(name, callback, scope) {
  1126. /**
  1127. * Callback function that gets called when a command is executed.
  1128. *
  1129. * @callback addCommandCallback
  1130. * @param {Boolean} ui Display UI state true/false.
  1131. * @param {Object} value Optional value for command.
  1132. * @return {Boolean} True/false state if the command was handled or not.
  1133. */
  1134. this.execCommands[name] = {func: callback, scope: scope || this};
  1135. },
  1136. /**
  1137. * Adds a custom query state command to the editor, you can also override existing commands with this method.
  1138. * The command that you add can be executed with queryCommandState function.
  1139. *
  1140. * @method addQueryStateHandler
  1141. * @param {String} name Command name to add/override.
  1142. * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
  1143. * @param {Object} scope Optional scope to execute the function in.
  1144. */
  1145. addQueryStateHandler: function(name, callback, scope) {
  1146. /**
  1147. * Callback function that gets called when a queryCommandState is executed.
  1148. *
  1149. * @callback addQueryStateHandlerCallback
  1150. * @return {Boolean} True/false state if the command is enabled or not like is it bold.
  1151. */
  1152. this.queryStateCommands[name] = {func: callback, scope: scope || this};
  1153. },
  1154. /**
  1155. * Adds a custom query value command to the editor, you can also override existing commands with this method.
  1156. * The command that you add can be executed with queryCommandValue function.
  1157. *
  1158. * @method addQueryValueHandler
  1159. * @param {String} name Command name to add/override.
  1160. * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
  1161. * @param {Object} scope Optional scope to execute the function in.
  1162. */
  1163. addQueryValueHandler: function(name, callback, scope) {
  1164. /**
  1165. * Callback function that gets called when a queryCommandValue is executed.
  1166. *
  1167. * @callback addQueryValueHandlerCallback
  1168. * @return {Object} Value of the command or undefined.
  1169. */
  1170. this.queryValueCommands[name] = {func: callback, scope: scope || this};
  1171. },
  1172. /**
  1173. * Adds a keyboard shortcut for some command or function.
  1174. *
  1175. * @method addShortcut
  1176. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  1177. * @param {String} desc Text description for the command.
  1178. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  1179. * @param {Object} sc Optional scope to execute the function in.
  1180. * @return {Boolean} true/false state if the shortcut was added or not.
  1181. */
  1182. addShortcut: function(pattern, desc, cmdFunc, scope) {
  1183. this.shortcuts.add(pattern, desc, cmdFunc, scope);
  1184. },
  1185. /**
  1186. * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
  1187. * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
  1188. * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
  1189. * return true it will handle the command as a internal browser command.
  1190. *
  1191. * @method execCommand
  1192. * @param {String} cmd Command name to execute, for example mceLink or Bold.
  1193. * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
  1194. * @param {mixed} value Optional command value, this can be anything.
  1195. * @param {Object} a Optional arguments object.
  1196. */
  1197. execCommand: function(cmd, ui, value, args) {
  1198. var self = this, state = 0, cmdItem;
  1199. if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
  1200. self.focus();
  1201. }
  1202. args = extend({}, args);
  1203. args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
  1204. if (args.isDefaultPrevented()) {
  1205. return false;
  1206. }
  1207. // Registred commands
  1208. if ((cmdItem = self.execCommands[cmd])) {
  1209. // Fall through on true
  1210. if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
  1211. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  1212. return true;
  1213. }
  1214. }
  1215. // Plugin commands
  1216. each(self.plugins, function(p) {
  1217. if (p.execCommand && p.execCommand(cmd, ui, value)) {
  1218. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  1219. state = true;
  1220. return false;
  1221. }
  1222. });
  1223. if (state) {
  1224. return state;
  1225. }
  1226. // Theme commands
  1227. if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
  1228. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  1229. return true;
  1230. }
  1231. // Editor commands
  1232. if (self.editorCommands.execCommand(cmd, ui, value)) {
  1233. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  1234. return true;
  1235. }
  1236. // Browser commands
  1237. self.getDoc().execCommand(cmd, ui, value);
  1238. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  1239. },
  1240. /**
  1241. * Returns a command specific state, for example if bold is enabled or not.
  1242. *
  1243. * @method queryCommandState
  1244. * @param {string} cmd Command to query state from.
  1245. * @return {Boolean} Command specific state, for example if bold is enabled or not.
  1246. */
  1247. queryCommandState: function(cmd) {
  1248. var self = this, queryItem, returnVal;
  1249. // Is hidden then return undefined
  1250. if (self._isHidden()) {
  1251. return;
  1252. }
  1253. // Registred commands
  1254. if ((queryItem = self.queryStateCommands[cmd])) {
  1255. returnVal = queryItem.func.call(queryItem.scope);
  1256. // Fall though on true
  1257. if (returnVal !== true) {
  1258. return returnVal;
  1259. }
  1260. }
  1261. // Editor commands
  1262. returnVal = self.editorCommands.queryCommandState(cmd);
  1263. if (returnVal !== -1) {
  1264. return returnVal;
  1265. }
  1266. // Browser commands
  1267. try {
  1268. return self.getDoc().queryCommandState(cmd);
  1269. } catch (ex) {
  1270. // Fails sometimes see bug: 1896577
  1271. }
  1272. },
  1273. /**
  1274. * Returns a command specific value, for example the current font size.
  1275. *
  1276. * @method queryCommandValue
  1277. * @param {string} cmd Command to query value from.
  1278. * @return {Object} Command specific value, for example the current font size.
  1279. */
  1280. queryCommandValue: function(cmd) {
  1281. var self = this, queryItem, returnVal;
  1282. // Is hidden then return undefined
  1283. if (self._isHidden()) {
  1284. return;
  1285. }
  1286. // Registred commands
  1287. if ((queryItem = self.queryValueCommands[cmd])) {
  1288. returnVal = queryItem.func.call(queryItem.scope);
  1289. // Fall though on true
  1290. if (returnVal !== true) {
  1291. return returnVal;
  1292. }
  1293. }
  1294. // Editor commands
  1295. returnVal = self.editorCommands.queryCommandValue(cmd);
  1296. if (returnVal !== undefined) {
  1297. return returnVal;
  1298. }
  1299. // Browser commands
  1300. try {
  1301. return self.getDoc().queryCommandValue(cmd);
  1302. } catch (ex) {
  1303. // Fails sometimes see bug: 1896577
  1304. }
  1305. },
  1306. /**
  1307. * Shows the editor and hides any textarea/div that the editor is supposed to replace.
  1308. *
  1309. * @method show
  1310. */
  1311. show: function() {
  1312. var self = this;
  1313. DOM.show(self.getContainer());
  1314. DOM.hide(self.id);
  1315. self.load();
  1316. self.fire('show');
  1317. },
  1318. /**
  1319. * Hides the editor and shows any textarea/div that the editor is supposed to replace.
  1320. *
  1321. * @method hide
  1322. */
  1323. hide: function() {
  1324. var self = this, doc = self.getDoc();
  1325. // Fixed bug where IE has a blinking cursor left from the editor
  1326. if (ie && doc && !self.inline) {
  1327. doc.execCommand('SelectAll');
  1328. }
  1329. // We must save before we hide so Safari doesn't crash
  1330. self.save();
  1331. // defer the call to hide to prevent an IE9 crash #4921
  1332. DOM.hide(self.getContainer());
  1333. DOM.setStyle(self.id, 'display', self.orgDisplay);
  1334. self.fire('hide');
  1335. },
  1336. /**
  1337. * Returns true/false if the editor is hidden or not.
  1338. *
  1339. * @method isHidden
  1340. * @return {Boolean} True/false if the editor is hidden or not.
  1341. */
  1342. isHidden: function() {
  1343. return !DOM.isHidden(this.id);
  1344. },
  1345. /**
  1346. * Sets the progress state, this will display a throbber/progess for the editor.
  1347. * This is ideal for asycronous operations like an AJAX save call.
  1348. *
  1349. * @method setProgressState
  1350. * @param {Boolean} state Boolean state if the progress should be shown or hidden.
  1351. * @param {Number} time Optional time to wait before the progress gets shown.
  1352. * @return {Boolean} Same as the input state.
  1353. * @example
  1354. * // Show progress for the active editor
  1355. * tinymce.activeEditor.setProgressState(true);
  1356. *
  1357. * // Hide progress for the active editor
  1358. * tinymce.activeEditor.setProgressState(false);
  1359. *
  1360. * // Show progress after 3 seconds
  1361. * tinymce.activeEditor.setProgressState(true, 3000);
  1362. */
  1363. setProgressState: function(state, time) {
  1364. this.fire('ProgressState', {state: state, time: time});
  1365. },
  1366. /**
  1367. * Loads contents from the textarea or div element that got converted into an editor instance.
  1368. * This method will move the contents from that textarea or div into the editor by using setContent
  1369. * so all events etc that method has will get dispatched as well.
  1370. *
  1371. * @method load
  1372. * @param {Object} args Optional content object, this gets passed around through the whole load process.
  1373. * @return {String} HTML string that got set into the editor.
  1374. */
  1375. load: function(args) {
  1376. var self = this, elm = self.getElement(), html;
  1377. if (elm) {
  1378. args = args || {};
  1379. args.load = true;
  1380. html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
  1381. args.element = elm;
  1382. if (!args.no_events) {
  1383. self.fire('LoadContent', args);
  1384. }
  1385. args.element = elm = null;
  1386. return html;
  1387. }
  1388. },
  1389. /**
  1390. * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
  1391. * This method will move the HTML contents from the editor into that textarea or div by getContent
  1392. * so all events etc that method has will get dispatched as well.
  1393. *
  1394. * @method save
  1395. * @param {Object} args Optional content object, this gets passed around through the whole save process.
  1396. * @return {String} HTML string that got set into the textarea/div.
  1397. */
  1398. save: function(args) {
  1399. var self = this, elm = self.getElement(), html, form;
  1400. if (!elm || !self.initialized) {
  1401. return;
  1402. }
  1403. args = args || {};
  1404. args.save = true;
  1405. args.element = elm;
  1406. html = args.content = self.getContent(args);
  1407. if (!args.no_events) {
  1408. self.fire('SaveContent', args);
  1409. }
  1410. html = args.content;
  1411. if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
  1412. // Update DIV element when not in inline mode
  1413. if (!self.inline) {
  1414. elm.innerHTML = html;
  1415. }
  1416. // Update hidden form element
  1417. if ((form = DOM.getParent(self.id, 'form'))) {
  1418. each(form.elements, function(elm) {
  1419. if (elm.name == self.id) {
  1420. elm.value = html;
  1421. return false;
  1422. }
  1423. });
  1424. }
  1425. } else {
  1426. elm.value = html;
  1427. }
  1428. args.element = elm = null;
  1429. if (args.set_dirty !== false) {
  1430. self.isNotDirty = true;
  1431. }
  1432. return html;
  1433. },
  1434. /**
  1435. * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
  1436. * the different cleanup rules options.
  1437. *
  1438. * @method setContent
  1439. * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
  1440. * @param {Object} args Optional content object, this gets passed around through the whole set process.
  1441. * @return {String} HTML string that got set into the editor.
  1442. * @example
  1443. * // Sets the HTML contents of the activeEditor editor
  1444. * tinymce.activeEditor.setContent('<span>some</span> html');
  1445. *
  1446. * // Sets the raw contents of the activeEditor editor
  1447. * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
  1448. *
  1449. * // Sets the content of a specific editor (my_editor in this example)
  1450. * tinymce.get('my_editor').setContent(data);
  1451. *
  1452. * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
  1453. * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
  1454. */
  1455. setContent: function(content, args) {
  1456. var self = this, body = self.getBody(), forcedRootBlockName;
  1457. // Setup args object
  1458. args = args || {};
  1459. args.format = args.format || 'html';
  1460. args.set = true;
  1461. args.content = content;
  1462. // Do preprocessing
  1463. if (!args.no_events) {
  1464. self.fire('BeforeSetContent', args);
  1465. }
  1466. content = args.content;
  1467. // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
  1468. // It will also be impossible to place the caret in the editor unless there is a BR element present
  1469. if (content.length === 0 || /^\s+$/.test(content)) {
  1470. forcedRootBlockName = self.settings.forced_root_block;
  1471. // Check if forcedRootBlock is configured and that the block is a valid child of the body
  1472. if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
  1473. // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
  1474. content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
  1475. content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
  1476. } else if (!ie) {
  1477. // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
  1478. content = '<br data-mce-bogus="1">';
  1479. }
  1480. body.innerHTML = content;
  1481. self.fire('SetContent', args);
  1482. } else {
  1483. // Parse and serialize the html
  1484. if (args.format !== 'raw') {
  1485. content = new Serializer({}, self.schema).serialize(
  1486. self.parser.parse(content, {isRootContent: true})
  1487. );
  1488. }
  1489. // Set the new cleaned contents to the editor
  1490. args.content = trim(content);
  1491. self.dom.setHTML(body, args.content);
  1492. // Do post processing
  1493. if (!args.no_events) {
  1494. self.fire('SetContent', args);
  1495. }
  1496. // Don't normalize selection if the focused element isn't the body in
  1497. // content editable mode since it will steal focus otherwise
  1498. /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
  1499. self.selection.normalize();
  1500. }*/
  1501. }
  1502. return args.content;
  1503. },
  1504. /**
  1505. * Gets the content from the editor instance, this will cleanup the content before it gets returned using
  1506. * the different cleanup rules options.
  1507. *
  1508. * @method getContent
  1509. * @param {Object} args Optional content object, this gets passed around through the whole get process.
  1510. * @return {String} Cleaned content string, normally HTML contents.
  1511. * @example
  1512. * // Get the HTML contents of the currently active editor
  1513. * console.debug(tinymce.activeEditor.getContent());
  1514. *
  1515. * // Get the raw contents of the currently active editor
  1516. * tinymce.activeEditor.getContent({format: 'raw'});
  1517. *
  1518. * // Get content of a specific editor:
  1519. * tinymce.get('content id').getContent()
  1520. */
  1521. getContent: function(args) {
  1522. var self = this, content, body = self.getBody();
  1523. // Setup args object
  1524. args = args || {};
  1525. args.format = args.format || 'html';
  1526. args.get = true;
  1527. args.getInner = true;
  1528. // Do preprocessing
  1529. if (!args.no_events) {
  1530. self.fire('BeforeGetContent', args);
  1531. }
  1532. // Get raw contents or by default the cleaned contents
  1533. if (args.format == 'raw') {
  1534. content = body.innerHTML;
  1535. } else if (args.format == 'text') {
  1536. content = body.innerText || body.textContent;
  1537. } else {
  1538. content = self.serializer.serialize(body, args);
  1539. }
  1540. // Trim whitespace in beginning/end of HTML
  1541. if (args.format != 'text') {
  1542. args.content = trim(content);
  1543. } else {
  1544. args.content = content;
  1545. }
  1546. // Do post processing
  1547. if (!args.no_events) {
  1548. self.fire('GetContent', args);
  1549. }
  1550. return args.content;
  1551. },
  1552. /**
  1553. * Inserts content at caret position.
  1554. *
  1555. * @method insertContent
  1556. * @param {String} content Content to insert.
  1557. */
  1558. insertContent: function(content) {
  1559. this.execCommand('mceInsertContent', false, content);
  1560. },
  1561. /**
  1562. * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  1563. *
  1564. * @method isDirty
  1565. * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  1566. * @example
  1567. * if (tinymce.activeEditor.isDirty())
  1568. * alert("You must save your contents.");
  1569. */
  1570. isDirty: function() {
  1571. return !this.isNotDirty;
  1572. },
  1573. /**
  1574. * Returns the editors container element. The container element wrappes in
  1575. * all the elements added to the page for the editor. Such as UI, iframe etc.
  1576. *
  1577. * @method getContainer
  1578. * @return {Element} HTML DOM element for the editor container.
  1579. */
  1580. getContainer: function() {
  1581. var self = this;
  1582. if (!self.container) {
  1583. self.container = DOM.get(self.editorContainer || self.id + '_parent');
  1584. }
  1585. return self.container;
  1586. },
  1587. /**
  1588. * Returns the editors content area container element. The this element is the one who
  1589. * holds the iframe or the editable element.
  1590. *
  1591. * @method getContentAreaContainer
  1592. * @return {Element} HTML DOM element for the editor area container.
  1593. */
  1594. getContentAreaContainer: function() {
  1595. return this.contentAreaContainer;
  1596. },
  1597. /**
  1598. * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
  1599. *
  1600. * @method getElement
  1601. * @return {Element} HTML DOM element for the replaced element.
  1602. */
  1603. getElement: function() {
  1604. return DOM.get(this.settings.content_element || this.id);
  1605. },
  1606. /**
  1607. * Returns the iframes window object.
  1608. *
  1609. * @method getWin
  1610. * @return {Window} Iframe DOM window object.
  1611. */
  1612. getWin: function() {
  1613. var self = this, elm;
  1614. if (!self.contentWindow) {
  1615. elm = DOM.get(self.id + "_ifr");
  1616. if (elm) {
  1617. self.contentWindow = elm.contentWindow;
  1618. }
  1619. }
  1620. return self.contentWindow;
  1621. },
  1622. /**
  1623. * Returns the iframes document object.
  1624. *
  1625. * @method getDoc
  1626. * @return {Document} Iframe DOM document object.
  1627. */
  1628. getDoc: function() {
  1629. var self = this, win;
  1630. if (!self.contentDocument) {
  1631. win = self.getWin();
  1632. if (win) {
  1633. self.contentDocument = win.document;
  1634. }
  1635. }
  1636. return self.contentDocument;
  1637. },
  1638. /**
  1639. * Returns the iframes body element.
  1640. *
  1641. * @method getBody
  1642. * @return {Element} Iframe body element.
  1643. */
  1644. getBody: function() {
  1645. return this.bodyElement || this.getDoc().body;
  1646. },
  1647. /**
  1648. * URL converter function this gets executed each time a user adds an img, a or
  1649. * any other element that has a URL in it. This will be called both by the DOM and HTML
  1650. * manipulation functions.
  1651. *
  1652. * @method convertURL
  1653. * @param {string} url URL to convert.
  1654. * @param {string} name Attribute name src, href etc.
  1655. * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
  1656. * @return {string} Converted URL string.
  1657. */
  1658. convertURL: function(url, name, elm) {
  1659. var self = this, settings = self.settings;
  1660. // Use callback instead
  1661. if (settings.urlconverter_callback) {
  1662. return self.execCallback('urlconverter_callback', url, elm, true, name);
  1663. }
  1664. // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
  1665. if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
  1666. return url;
  1667. }
  1668. // Convert to relative
  1669. if (settings.relative_urls) {
  1670. return self.documentBaseURI.toRelative(url);
  1671. }
  1672. // Convert to absolute
  1673. url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
  1674. return url;
  1675. },
  1676. /**
  1677. * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
  1678. *
  1679. * @method addVisual
  1680. * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
  1681. */
  1682. addVisual: function(elm) {
  1683. var self = this, settings = self.settings, dom = self.dom, cls;
  1684. elm = elm || self.getBody();
  1685. if (self.hasVisual === undefined) {
  1686. self.hasVisual = settings.visual;
  1687. }
  1688. each(dom.select('table,a', elm), function(elm) {
  1689. var value;
  1690. switch (elm.nodeName) {
  1691. case 'TABLE':
  1692. cls = settings.visual_table_class || 'mce-item-table';
  1693. value = dom.getAttrib(elm, 'border');
  1694. if (!value || value == '0') {
  1695. if (self.hasVisual) {
  1696. dom.addClass(elm, cls);
  1697. } else {
  1698. dom.removeClass(elm, cls);
  1699. }
  1700. }
  1701. return;
  1702. case 'A':
  1703. if (!dom.getAttrib(elm, 'href', false)) {
  1704. value = dom.getAttrib(elm, 'name') || elm.id;
  1705. cls = settings.visual_anchor_class || 'mce-item-anchor';
  1706. if (value) {
  1707. if (self.hasVisual) {
  1708. dom.addClass(elm, cls);
  1709. } else {
  1710. dom.removeClass(elm, cls);
  1711. }
  1712. }
  1713. }
  1714. return;
  1715. }
  1716. });
  1717. self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
  1718. },
  1719. /**
  1720. * Removes the editor from the dom and tinymce collection.
  1721. *
  1722. * @method remove
  1723. */
  1724. remove: function() {
  1725. var self = this;
  1726. if (!self.removed) {
  1727. self.removed = 1;
  1728. self.save();
  1729. // Remove any hidden input
  1730. if (self.hasHiddenInput) {
  1731. DOM.remove(self.getElement().nextSibling);
  1732. }
  1733. if (!self.inline) {
  1734. // IE 9 has a bug where the selection stops working if you place the
  1735. // caret inside the editor then remove the iframe
  1736. if (ie && ie < 10) {
  1737. self.getDoc().execCommand('SelectAll', false, null);
  1738. }
  1739. DOM.setStyle(self.id, 'display', self.orgDisplay);
  1740. self.getBody().onload = null; // Prevent #6816
  1741. // Don't clear the window or document if content editable
  1742. // is enabled since other instances might still be present
  1743. Event.unbind(self.getWin());
  1744. Event.unbind(self.getDoc());
  1745. }
  1746. var elm = self.getContainer();
  1747. Event.unbind(self.getBody());
  1748. Event.unbind(elm);
  1749. self.fire('remove');
  1750. self.editorManager.remove(self);
  1751. DOM.remove(elm);
  1752. self.destroy();
  1753. }
  1754. },
  1755. bindNative: function(name) {
  1756. var self = this;
  1757. if (self.settings.readonly) {
  1758. return;
  1759. }
  1760. if (self.initialized) {
  1761. self.dom.bind(getEventTarget(self, name), name, function(e) {
  1762. self.fire(name, e);
  1763. });
  1764. } else {
  1765. if (!self._pendingNativeEvents) {
  1766. self._pendingNativeEvents = [name];
  1767. } else {
  1768. self._pendingNativeEvents.push(name);
  1769. }
  1770. }
  1771. },
  1772. unbindNative: function(name) {
  1773. var self = this;
  1774. if (self.initialized) {
  1775. self.dom.unbind(name);
  1776. }
  1777. },
  1778. /**
  1779. * Destroys the editor instance by removing all events, element references or other resources
  1780. * that could leak memory. This method will be called automatically when the page is unloaded
  1781. * but you can also call it directly if you know what you are doing.
  1782. *
  1783. * @method destroy
  1784. * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
  1785. */
  1786. destroy: function(automatic) {
  1787. var self = this, form;
  1788. // One time is enough
  1789. if (self.destroyed) {
  1790. return;
  1791. }
  1792. // If user manually calls destroy and not remove
  1793. // Users seems to have logic that calls destroy instead of remove
  1794. if (!automatic && !self.removed) {
  1795. self.remove();
  1796. return;
  1797. }
  1798. // We must unbind on Gecko since it would otherwise produce the pesky "attempt
  1799. // to run compile-and-go script on a cleared scope" message
  1800. if (automatic && isGecko) {
  1801. Event.unbind(self.getDoc());
  1802. Event.unbind(self.getWin());
  1803. Event.unbind(self.getBody());
  1804. }
  1805. if (!automatic) {
  1806. self.editorManager.off('beforeunload', self._beforeUnload);
  1807. // Manual destroy
  1808. if (self.theme && self.theme.destroy) {
  1809. self.theme.destroy();
  1810. }
  1811. // Destroy controls, selection and dom
  1812. self.selection.destroy();
  1813. self.dom.destroy();
  1814. }
  1815. form = self.formElement;
  1816. if (form) {
  1817. if (form._mceOldSubmit) {
  1818. form.submit = form._mceOldSubmit;
  1819. form._mceOldSubmit = null;
  1820. }
  1821. DOM.unbind(form, 'submit reset', self.formEventDelegate);
  1822. }
  1823. self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
  1824. self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null;
  1825. if (self.selection) {
  1826. self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
  1827. }
  1828. self.destroyed = 1;
  1829. },
  1830. // Internal functions
  1831. _refreshContentEditable: function() {
  1832. var self = this, body, parent;
  1833. // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
  1834. if (self._isHidden()) {
  1835. body = self.getBody();
  1836. parent = body.parentNode;
  1837. parent.removeChild(body);
  1838. parent.appendChild(body);
  1839. body.focus();
  1840. }
  1841. },
  1842. _isHidden: function() {
  1843. var sel;
  1844. if (!isGecko) {
  1845. return 0;
  1846. }
  1847. // Weird, wheres that cursor selection?
  1848. sel = this.selection.getSel();
  1849. return (!sel || !sel.rangeCount || sel.rangeCount === 0);
  1850. }
  1851. };
  1852. extend(Editor.prototype, Observable);
  1853. return Editor;
  1854. });