| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181 |
- /**
- * Editor.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*jshint scripturl:true */
- /**
- * Include the base event class documentation.
- *
- * @include ../../../tools/docs/tinymce.Event.js
- */
- /**
- * This class contains the core logic for a TinyMCE editor.
- *
- * @class tinymce.Editor
- * @mixes tinymce.util.Observable
- * @example
- * // Add a class to all paragraphs in the editor.
- * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
- *
- * // Gets the current editors selection as text
- * tinymce.activeEditor.selection.getContent({format: 'text'});
- *
- * // Creates a new editor instance
- * var ed = new tinymce.Editor('textareaid', {
- * some_setting: 1
- * }, tinymce.EditorManager);
- *
- * // Select each item the user clicks on
- * ed.on('click', function(e) {
- * ed.selection.select(e.target);
- * });
- *
- * ed.render();
- */
- define("tinymce/Editor", [
- "tinymce/dom/DOMUtils",
- "tinymce/AddOnManager",
- "tinymce/html/Node",
- "tinymce/dom/Serializer",
- "tinymce/html/Serializer",
- "tinymce/dom/Selection",
- "tinymce/Formatter",
- "tinymce/UndoManager",
- "tinymce/EnterKey",
- "tinymce/ForceBlocks",
- "tinymce/EditorCommands",
- "tinymce/util/URI",
- "tinymce/dom/ScriptLoader",
- "tinymce/dom/EventUtils",
- "tinymce/WindowManager",
- "tinymce/html/Schema",
- "tinymce/html/DomParser",
- "tinymce/util/Quirks",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/util/Observable",
- "tinymce/Shortcuts"
- ], function(
- DOMUtils, AddOnManager, Node, DomSerializer, Serializer,
- Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
- URI, ScriptLoader, EventUtils, WindowManager,
- Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts
- ) {
- // Shorten these names
- var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
- var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
- var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
- var Event = EventUtils.Event;
- var isGecko = Env.gecko, ie = Env.ie;
- function getEventTarget(editor, eventName) {
- if (eventName == 'selectionchange') {
- return editor.getDoc();
- }
- // Need to bind mousedown/mouseup etc to document not body in iframe mode
- // Since the user might click on the HTML element not the BODY
- if (!editor.inline && /^mouse|click|contextmenu|drop/.test(eventName)) {
- return editor.getDoc();
- }
- return editor.getBody();
- }
- /**
- * Include documentation for all the events.
- *
- * @include ../../../tools/docs/tinymce.Editor.js
- */
- /**
- * Constructs a editor instance by id.
- *
- * @constructor
- * @method Editor
- * @param {String} id Unique id for the editor.
- * @param {Object} settings Settings for the editor.
- * @param {tinymce.EditorManager} editorManager EditorManager instance.
- * @author Moxiecode
- */
- function Editor(id, settings, editorManager) {
- var self = this, documentBaseUrl, baseUri;
- documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
- baseUri = editorManager.baseURI;
- /**
- * Name/value collection with editor settings.
- *
- * @property settings
- * @type Object
- * @example
- * // Get the value of the theme setting
- * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
- */
- self.settings = settings = extend({
- id: id,
- theme: 'modern',
- delta_width: 0,
- delta_height: 0,
- popup_css: '',
- plugins: '',
- document_base_url: documentBaseUrl,
- add_form_submit_trigger: true,
- submit_patch: true,
- add_unload_trigger: true,
- convert_urls: true,
- relative_urls: true,
- remove_script_host: true,
- object_resizing: true,
- doctype: '<!DOCTYPE html>',
- visual: true,
- font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
- // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
- font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
- forced_root_block: 'p',
- hidden_input: true,
- padd_empty_editor: true,
- render_ui: true,
- indentation: '30px',
- inline_styles: true,
- convert_fonts_to_spans: true,
- indent: 'simple',
- indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
- indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
- validate: true,
- entity_encoding: 'named',
- url_converter: self.convertURL,
- url_converter_scope: self,
- ie7_compat: true
- }, settings);
- AddOnManager.language = settings.language || 'en';
- AddOnManager.languageLoad = settings.language_load;
- AddOnManager.baseURL = editorManager.baseURL;
- /**
- * Editor instance id, normally the same as the div/textarea that was replaced.
- *
- * @property id
- * @type String
- */
- self.id = settings.id = id;
- /**
- * State to force the editor to return false on a isDirty call.
- *
- * @property isNotDirty
- * @type Boolean
- * @example
- * function ajaxSave() {
- * var ed = tinymce.get('elm1');
- *
- * // Save contents using some XHR call
- * alert(ed.getContent());
- *
- * ed.isNotDirty = true; // Force not dirty state
- * }
- */
- self.isNotDirty = true;
- /**
- * Name/Value object containting plugin instances.
- *
- * @property plugins
- * @type Object
- * @example
- * // Execute a method inside a plugin directly
- * tinymce.activeEditor.plugins.someplugin.someMethod();
- */
- self.plugins = {};
- /**
- * URI object to document configured for the TinyMCE instance.
- *
- * @property documentBaseURI
- * @type tinymce.util.URI
- * @example
- * // Get relative URL from the location of document_base_url
- * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
- *
- * // Get absolute URL from the location of document_base_url
- * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
- */
- self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
- base_uri: baseUri
- });
- /**
- * URI object to current document that holds the TinyMCE editor instance.
- *
- * @property baseURI
- * @type tinymce.util.URI
- * @example
- * // Get relative URL from the location of the API
- * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
- *
- * // Get absolute URL from the location of the API
- * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
- */
- self.baseURI = baseUri;
- /**
- * Array with CSS files to load into the iframe.
- *
- * @property contentCSS
- * @type Array
- */
- self.contentCSS = [];
- /**
- * Array of CSS styles to add to head of document when the editor loads.
- *
- * @property contentStyles
- * @type Array
- */
- self.contentStyles = [];
- // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
- self.shortcuts = new Shortcuts(self);
- // Internal command handler objects
- self.execCommands = {};
- self.queryStateCommands = {};
- self.queryValueCommands = {};
- self.loadedCSS = {};
- self.suffix = editorManager.suffix;
- self.editorManager = editorManager;
- self.inline = settings.inline;
- // Call setup
- editorManager.fire('SetupEditor', self);
- self.execCallback('setup', self);
- }
- Editor.prototype = {
- /**
- * Renderes the editor/adds it to the page.
- *
- * @method render
- */
- render: function() {
- var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
- function readyHandler() {
- DOM.unbind(window, 'ready', readyHandler);
- self.render();
- }
- // Page is not loaded yet, wait for it
- if (!Event.domLoaded) {
- DOM.bind(window, 'ready', readyHandler);
- return;
- }
- // Element not found, then skip initialization
- if (!self.getElement()) {
- return;
- }
- // No editable support old iOS versions etc
- if (!Env.contentEditable) {
- return;
- }
- // Hide target element early to prevent content flashing
- if (!settings.inline) {
- self.orgVisibility = self.getElement().style.visibility;
- self.getElement().style.visibility = 'hidden';
- } else {
- self.inline = true;
- }
- var form = self.getElement().form || DOM.getParent(id, 'form');
- if (form) {
- self.formElement = form;
- // Add hidden input for non input elements inside form elements
- if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
- DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
- self.hasHiddenInput = true;
- }
- // Pass submit/reset from form to editor instance
- self.formEventDelegate = function(e) {
- self.fire(e.type, e);
- };
- DOM.bind(form, 'submit reset', self.formEventDelegate);
- // Reset contents in editor when the form is reset
- self.on('reset', function() {
- self.setContent(self.startContent, {format: 'raw'});
- });
- // Check page uses id="submit" or name="submit" for it's submit button
- if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
- form._mceOldSubmit = form.submit;
- form.submit = function() {
- self.editorManager.triggerSave();
- self.isNotDirty = true;
- return form._mceOldSubmit(form);
- };
- }
- }
- /**
- * Window manager reference, use this to open new windows and dialogs.
- *
- * @property windowManager
- * @type tinymce.WindowManager
- * @example
- * // Shows an alert message
- * tinymce.activeEditor.windowManager.alert('Hello world!');
- *
- * // Opens a new dialog with the file.htm file and the size 320x240
- * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
- * tinymce.activeEditor.windowManager.open({
- * url: 'file.htm',
- * width: 320,
- * height: 240
- * }, {
- * custom_param: 1
- * });
- */
- self.windowManager = new WindowManager(self);
- if (settings.encoding == 'xml') {
- self.on('GetContent', function(e) {
- if (e.save) {
- e.content = DOM.encode(e.content);
- }
- });
- }
- if (settings.add_form_submit_trigger) {
- self.on('submit', function() {
- if (self.initialized) {
- self.save();
- }
- });
- }
- if (settings.add_unload_trigger) {
- self._beforeUnload = function() {
- if (self.initialized && !self.destroyed && !self.isHidden()) {
- self.save({format: 'raw', no_events: true, set_dirty: false});
- }
- };
- self.editorManager.on('BeforeUnload', self._beforeUnload);
- }
- // Load scripts
- function loadScripts() {
- var scriptLoader = ScriptLoader.ScriptLoader;
- if (settings.language && settings.language != 'en' && !settings.language_url) {
- settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
- }
- if (settings.language_url) {
- scriptLoader.add(settings.language_url);
- }
- if (settings.theme && typeof settings.theme != "function" &&
- settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
- var themeUrl = settings.theme_url;
- if (themeUrl) {
- themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
- } else {
- themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
- }
- ThemeManager.load(settings.theme, themeUrl);
- }
- if (Tools.isArray(settings.plugins)) {
- settings.plugins = settings.plugins.join(' ');
- }
- each(settings.external_plugins, function(url, name) {
- PluginManager.load(name, url);
- settings.plugins += ' ' + name;
- });
- each(settings.plugins.split(/[ ,]/), function(plugin) {
- plugin = trim(plugin);
- if (plugin && !PluginManager.urls[plugin]) {
- if (plugin.charAt(0) == '-') {
- plugin = plugin.substr(1, plugin.length);
- var dependencies = PluginManager.dependencies(plugin);
- each(dependencies, function(dep) {
- var defaultSettings = {
- prefix:'plugins/',
- resource: dep,
- suffix:'/plugin' + suffix + '.js'
- };
- dep = PluginManager.createUrl(defaultSettings, dep);
- PluginManager.load(dep.resource, dep);
- });
- } else {
- PluginManager.load(plugin, {
- prefix: 'plugins/',
- resource: plugin,
- suffix: '/plugin' + suffix + '.js'
- });
- }
- }
- });
- scriptLoader.loadQueue(function() {
- if (!self.removed) {
- self.init();
- }
- });
- }
- loadScripts();
- },
- /**
- * Initializes the editor this will be called automatically when
- * all plugins/themes and language packs are loaded by the rendered method.
- * This method will setup the iframe and create the theme and plugin instances.
- *
- * @method init
- */
- init: function() {
- var self = this, settings = self.settings, elm = self.getElement();
- var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
- self.rtl = this.editorManager.i18n.rtl;
- self.editorManager.add(self);
- settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
- /**
- * Reference to the theme instance that was used to generate the UI.
- *
- * @property theme
- * @type tinymce.Theme
- * @example
- * // Executes a method on the theme directly
- * tinymce.activeEditor.theme.someMethod();
- */
- if (settings.theme) {
- if (typeof settings.theme != "function") {
- settings.theme = settings.theme.replace(/-/, '');
- Theme = ThemeManager.get(settings.theme);
- self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
- if (self.theme.init) {
- self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''));
- }
- } else {
- self.theme = settings.theme;
- }
- }
- function initPlugin(plugin) {
- var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
- pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
- plugin = trim(plugin);
- if (Plugin && inArray(initializedPlugins, plugin) === -1) {
- each(PluginManager.dependencies(plugin), function(dep){
- initPlugin(dep);
- });
- pluginInstance = new Plugin(self, pluginUrl);
- self.plugins[plugin] = pluginInstance;
- if (pluginInstance.init) {
- pluginInstance.init(self, pluginUrl);
- initializedPlugins.push(plugin);
- }
- }
- }
- // Create all plugins
- each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
- // Measure box
- if (settings.render_ui && self.theme) {
- self.orgDisplay = elm.style.display;
- if (typeof settings.theme != "function") {
- w = settings.width || elm.style.width || elm.offsetWidth;
- h = settings.height || elm.style.height || elm.offsetHeight;
- minHeight = settings.min_height || 100;
- re = /^[0-9\.]+(|px)$/i;
- if (re.test('' + w)) {
- w = Math.max(parseInt(w, 10), 100);
- }
- if (re.test('' + h)) {
- h = Math.max(parseInt(h, 10), minHeight);
- }
- // Render UI
- o = self.theme.renderUI({
- targetNode: elm,
- width: w,
- height: h,
- deltaWidth: settings.delta_width,
- deltaHeight: settings.delta_height
- });
- // Resize editor
- if (!settings.content_editable) {
- DOM.setStyles(o.sizeContainer || o.editorContainer, {
- wi2dth: w,
- // TODO: Fix this
- h2eight: h
- });
- h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
- if (h < minHeight) {
- h = minHeight;
- }
- }
- } else {
- o = settings.theme(self, elm);
- // Convert element type to id:s
- if (o.editorContainer.nodeType) {
- o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
- }
- // Convert element type to id:s
- if (o.iframeContainer.nodeType) {
- o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
- }
- // Use specified iframe height or the targets offsetHeight
- h = o.iframeHeight || elm.offsetHeight;
- }
- self.editorContainer = o.editorContainer;
- }
- // Load specified content CSS last
- if (settings.content_css) {
- each(explode(settings.content_css), function(u) {
- self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
- });
- }
- // Load specified content CSS last
- if (settings.content_style) {
- self.contentStyles.push(settings.content_style);
- }
- // Content editable mode ends here
- if (settings.content_editable) {
- elm = n = o = null; // Fix IE leak
- return self.initContentBody();
- }
- self.iframeHTML = settings.doctype + '<html><head>';
- // We only need to override paths if we have to
- // IE has a bug where it remove site absolute urls to relative ones if this is specified
- if (settings.document_base_url != self.documentBaseUrl) {
- self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
- }
- // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
- if (!Env.caretAfter && settings.ie7_compat) {
- self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
- }
- self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
- // Load the CSS by injecting them into the HTML this will reduce "flicker"
- for (i = 0; i < self.contentCSS.length; i++) {
- var cssUrl = self.contentCSS[i];
- self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />';
- self.loadedCSS[cssUrl] = true;
- }
- bodyId = settings.body_id || 'tinymce';
- if (bodyId.indexOf('=') != -1) {
- bodyId = self.getParam('body_id', '', 'hash');
- bodyId = bodyId[self.id] || bodyId;
- }
- bodyClass = settings.body_class || '';
- if (bodyClass.indexOf('=') != -1) {
- bodyClass = self.getParam('body_class', '', 'hash');
- bodyClass = bodyClass[self.id] || '';
- }
- self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" ' +
- 'onload="window.parent.tinymce.get(\'' + self.id + '\').fire(\'load\');"><br></body></html>';
- /*eslint no-script-url:0 */
- var domainRelaxUrl = 'javascript:(function(){' +
- 'document.open();document.domain="' + document.domain + '";' +
- 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
- 'document.close();ed.initContentBody(true);})()';
- // Domain relaxing is required since the user has messed around with document.domain
- if (document.domain != location.hostname) {
- url = domainRelaxUrl;
- }
- // Create iframe
- // TODO: ACC add the appropriate description on this.
- n = DOM.add(o.iframeContainer, 'iframe', {
- id: self.id + "_ifr",
- src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
- frameBorder: '0',
- allowTransparency: "true",
- title: self.editorManager.translate(
- "Rich Text Area. Press ALT-F9 for menu. " +
- "Press ALT-F10 for toolbar. Press ALT-0 for help"
- ),
- style: {
- width: '100%',
- height: h,
- display: 'block' // Important for Gecko to render the iframe correctly
- }
- });
- // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
- // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
- if (ie) {
- try {
- self.getDoc();
- } catch (e) {
- n.src = url = domainRelaxUrl;
- }
- }
- self.contentAreaContainer = o.iframeContainer;
- if (o.editorContainer) {
- DOM.get(o.editorContainer).style.display = self.orgDisplay;
- }
- DOM.get(self.id).style.display = 'none';
- DOM.setAttrib(self.id, 'aria-hidden', true);
- if (!url) {
- self.initContentBody();
- }
- elm = n = o = null; // Cleanup
- },
- /**
- * This method get called by the init method ones the iframe is loaded.
- * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
- *
- * @method initContentBody
- * @private
- */
- initContentBody: function(skipWrite) {
- var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText;
- // Restore visibility on target element
- if (!settings.inline) {
- self.getElement().style.visibility = self.orgVisibility;
- }
- // Setup iframe body
- if (!skipWrite && !settings.content_editable) {
- doc.open();
- doc.write(self.iframeHTML);
- doc.close();
- }
- if (settings.content_editable) {
- self.on('remove', function() {
- var bodyEl = this.getBody();
- DOM.removeClass(bodyEl, 'mce-content-body');
- DOM.removeClass(bodyEl, 'mce-edit-focus');
- DOM.setAttrib(bodyEl, 'contentEditable', null);
- });
- DOM.addClass(targetElm, 'mce-content-body');
- self.contentDocument = doc = settings.content_document || document;
- self.contentWindow = settings.content_window || window;
- self.bodyElement = targetElm;
- // Prevent leak in IE
- settings.content_document = settings.content_window = null;
- // TODO: Fix this
- settings.root_name = targetElm.nodeName.toLowerCase();
- }
- // It will not steal focus while setting contentEditable
- body = self.getBody();
- body.disabled = true;
- if (!settings.readonly) {
- if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
- body.style.position = 'relative';
- }
- body.contentEditable = self.getParam('content_editable_state', true);
- }
- body.disabled = false;
- /**
- * Schema instance, enables you to validate elements and it's children.
- *
- * @property schema
- * @type tinymce.html.Schema
- */
- self.schema = new Schema(settings);
- /**
- * DOM instance for the editor.
- *
- * @property dom
- * @type tinymce.dom.DOMUtils
- * @example
- * // Adds a class to all paragraphs within the editor
- * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
- */
- self.dom = new DOMUtils(doc, {
- keep_values: true,
- url_converter: self.convertURL,
- url_converter_scope: self,
- hex_colors: settings.force_hex_style_colors,
- class_filter: settings.class_filter,
- update_styles: true,
- root_element: settings.content_editable ? self.id : null,
- collect: settings.content_editable,
- schema: self.schema,
- onSetAttrib: function(e) {
- self.fire('SetAttrib', e);
- }
- });
- /**
- * HTML parser will be used when contents is inserted into the editor.
- *
- * @property parser
- * @type tinymce.html.DomParser
- */
- self.parser = new DomParser(settings, self.schema);
- // Convert src and href into data-mce-src, data-mce-href and data-mce-style
- self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
- var i = nodes.length, node, dom = self.dom, value, internalName;
- while (i--) {
- node = nodes[i];
- value = node.attr(name);
- internalName = 'data-mce-' + name;
- // Add internal attribute if we need to we don't on a refresh of the document
- if (!node.attributes.map[internalName]) {
- if (name === "style") {
- node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
- } else if (name === "tabindex") {
- node.attr(internalName, value);
- node.attr(name, null);
- } else {
- node.attr(internalName, self.convertURL(value, name, node.name));
- }
- }
- }
- });
- // Keep scripts from executing
- self.parser.addNodeFilter('script', function(nodes) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i];
- node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
- }
- });
- self.parser.addNodeFilter('#cdata', function(nodes) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i];
- node.type = 8;
- node.name = '#comment';
- node.value = '[CDATA[' + node.value + ']]';
- }
- });
- self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
- var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
- while (i--) {
- node = nodes[i];
- if (node.isEmpty(nonEmptyElements)) {
- node.empty().append(new Node('br', 1)).shortEnded = true;
- }
- }
- });
- /**
- * DOM serializer for the editor. Will be used when contents is extracted from the editor.
- *
- * @property serializer
- * @type tinymce.dom.Serializer
- * @example
- * // Serializes the first paragraph in the editor into a string
- * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
- */
- self.serializer = new DomSerializer(settings, self);
- /**
- * Selection instance for the editor.
- *
- * @property selection
- * @type tinymce.dom.Selection
- * @example
- * // Sets some contents to the current selection in the editor
- * tinymce.activeEditor.selection.setContent('Some contents');
- *
- * // Gets the current selection
- * alert(tinymce.activeEditor.selection.getContent());
- *
- * // Selects the first paragraph found
- * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
- */
- self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
- /**
- * Formatter instance.
- *
- * @property formatter
- * @type tinymce.Formatter
- */
- self.formatter = new Formatter(self);
- /**
- * Undo manager instance, responsible for handling undo levels.
- *
- * @property undoManager
- * @type tinymce.UndoManager
- * @example
- * // Undoes the last modification to the editor
- * tinymce.activeEditor.undoManager.undo();
- */
- self.undoManager = new UndoManager(self);
- self.forceBlocks = new ForceBlocks(self);
- self.enterKey = new EnterKey(self);
- self.editorCommands = new EditorCommands(self);
- self.fire('PreInit');
- if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
- doc.body.spellcheck = false; // Gecko
- DOM.setAttrib(body, "spellcheck", "false");
- }
- self.fire('PostRender');
- self.quirks = Quirks(self);
- if (settings.directionality) {
- body.dir = settings.directionality;
- }
- if (settings.nowrap) {
- body.style.whiteSpace = "nowrap";
- }
- if (settings.protect) {
- self.on('BeforeSetContent', function(e) {
- each(settings.protect, function(pattern) {
- e.content = e.content.replace(pattern, function(str) {
- return '<!--mce:protected ' + escape(str) + '-->';
- });
- });
- });
- }
- self.on('SetContent', function() {
- self.addVisual(self.getBody());
- });
- // Remove empty contents
- if (settings.padd_empty_editor) {
- self.on('PostProcess', function(e) {
- e.content = e.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
- });
- }
- self.load({initial: true, format: 'html'});
- self.startContent = self.getContent({format: 'raw'});
- /**
- * Is set to true after the editor instance has been initialized
- *
- * @property initialized
- * @type Boolean
- * @example
- * function isEditorInitialized(editor) {
- * return editor && editor.initialized;
- * }
- */
- self.initialized = true;
- each(self._pendingNativeEvents, function(name) {
- self.dom.bind(getEventTarget(self, name), name, function(e) {
- self.fire(e.type, e);
- });
- });
- self.fire('init');
- self.focus(true);
- self.nodeChanged({initial: true});
- self.execCallback('init_instance_callback', self);
- // Add editor specific CSS styles
- if (self.contentStyles.length > 0) {
- contentCssText = '';
- each(self.contentStyles, function(style) {
- contentCssText += style + "\r\n";
- });
- self.dom.addStyle(contentCssText);
- }
- // Load specified content CSS last
- each(self.contentCSS, function(cssUrl) {
- if (!self.loadedCSS[cssUrl]) {
- self.dom.loadCSS(cssUrl);
- self.loadedCSS[cssUrl] = true;
- }
- });
- // Handle auto focus
- if (settings.auto_focus) {
- setTimeout(function () {
- var ed = self.editorManager.get(settings.auto_focus);
- ed.selection.select(ed.getBody(), 1);
- ed.selection.collapse(1);
- ed.getBody().focus();
- ed.getWin().focus();
- }, 100);
- }
- // Clean up references for IE
- targetElm = doc = body = null;
- },
- /**
- * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
- * it will also place DOM focus inside the editor.
- *
- * @method focus
- * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
- */
- focus: function(skip_focus) {
- var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
- var controlElm, doc = self.getDoc(), body;
- if (!skip_focus) {
- // Get selected control element
- rng = selection.getRng();
- if (rng.item) {
- controlElm = rng.item(0);
- }
- self._refreshContentEditable();
- // Focus the window iframe
- if (!contentEditable) {
- // WebKit needs this call to fire focusin event properly see #5948
- // But Opera pre Blink engine will produce an empty selection so skip Opera
- if (!Env.opera) {
- self.getBody().focus();
- }
- self.getWin().focus();
- }
- // Focus the body as well since it's contentEditable
- if (isGecko || contentEditable) {
- body = self.getBody();
- // Check for setActive since it doesn't scroll to the element
- if (body.setActive) {
- // IE 11 sometimes throws "Invalid function" then fallback to focus
- try {
- body.setActive();
- } catch (ex) {
- body.focus();
- }
- } else {
- body.focus();
- }
- if (contentEditable) {
- selection.normalize();
- }
- }
- // Restore selected control element
- // This is needed when for example an image is selected within a
- // layer a call to focus will then remove the control selection
- if (controlElm && controlElm.ownerDocument == doc) {
- rng = doc.body.createControlRange();
- rng.addElement(controlElm);
- rng.select();
- }
- }
- if (self.editorManager.activeEditor != self) {
- if ((oed = self.editorManager.activeEditor)) {
- oed.fire('deactivate', {relatedTarget: self});
- }
- self.fire('activate', {relatedTarget: oed});
- }
- self.editorManager.activeEditor = self;
- },
- /**
- * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
- * There new event model is a better way to add callback so this method might be removed in the future.
- *
- * @method execCallback
- * @param {String} name Name of the callback to execute.
- * @return {Object} Return value passed from callback function.
- */
- execCallback: function(name) {
- var self = this, callback = self.settings[name], scope;
- if (!callback) {
- return;
- }
- // Look through lookup
- if (self.callbackLookup && (scope = self.callbackLookup[name])) {
- callback = scope.func;
- scope = scope.scope;
- }
- if (typeof(callback) === 'string') {
- scope = callback.replace(/\.\w+$/, '');
- scope = scope ? resolve(scope) : 0;
- callback = resolve(callback);
- self.callbackLookup = self.callbackLookup || {};
- self.callbackLookup[name] = {func: callback, scope: scope};
- }
- return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
- },
- /**
- * Translates the specified string by replacing variables with language pack items it will also check if there is
- * a key mathcin the input.
- *
- * @method translate
- * @param {String} text String to translate by the language pack data.
- * @return {String} Translated string.
- */
- translate: function(text) {
- var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
- if (!text) {
- return '';
- }
- return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
- return i18n.data[lang + '.' + b] || '{#' + b + '}';
- });
- },
- /**
- * Returns a language pack item by name/key.
- *
- * @method getLang
- * @param {String} name Name/key to get from the language pack.
- * @param {String} defaultVal Optional default value to retrive.
- */
- getLang: function(name, defaultVal) {
- return (
- this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
- (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
- );
- },
- /**
- * Returns a configuration parameter by name.
- *
- * @method getParam
- * @param {String} name Configruation parameter to retrive.
- * @param {String} defaultVal Optional default value to return.
- * @param {String} type Optional type parameter.
- * @return {String} Configuration parameter value or default value.
- * @example
- * // Returns a specific config value from the currently active editor
- * var someval = tinymce.activeEditor.getParam('myvalue');
- *
- * // Returns a specific config value from a specific editor instance by id
- * var someval2 = tinymce.get('my_editor').getParam('myvalue');
- */
- getParam: function(name, defaultVal, type) {
- var value = name in this.settings ? this.settings[name] : defaultVal, output;
- if (type === 'hash') {
- output = {};
- if (typeof(value) === 'string') {
- each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
- value = value.split('=');
- if (value.length > 1) {
- output[trim(value[0])] = trim(value[1]);
- } else {
- output[trim(value[0])] = trim(value);
- }
- });
- } else {
- output = value;
- }
- return output;
- }
- return value;
- },
- /**
- * Distpaches out a onNodeChange event to all observers. This method should be called when you
- * need to update the UI states or element path etc.
- *
- * @method nodeChanged
- */
- nodeChanged: function() {
- var self = this, selection = self.selection, node, parents, root;
- // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
- if (self.initialized && !self.settings.disable_nodechange && !self.settings.readonly) {
- // Get start node
- root = self.getBody();
- node = selection.getStart() || root;
- node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
- // Edge case for <p>|<img></p>
- if (node.nodeName == 'IMG' && selection.isCollapsed()) {
- node = node.parentNode;
- }
- // Get parents and add them to object
- parents = [];
- self.dom.getParent(node, function(node) {
- if (node === root) {
- return true;
- }
- parents.push(node);
- });
- self.fire('NodeChange', {element: node, parents: parents});
- }
- },
- /**
- * Adds a button that later gets created by the theme in the editors toolbars.
- *
- * @method addButton
- * @param {String} name Button name to add.
- * @param {Object} settings Settings object with title, cmd etc.
- * @example
- * // Adds a custom button to the editor that inserts contents when clicked
- * tinymce.init({
- * ...
- *
- * toolbar: 'example'
- *
- * setup: function(ed) {
- * ed.addButton('example', {
- * title: 'My title',
- * image: '../js/tinymce/plugins/example/img/example.gif',
- * onclick: function() {
- * ed.insertContent('Hello world!!');
- * }
- * });
- * }
- * });
- */
- addButton: function(name, settings) {
- var self = this;
- if (settings.cmd) {
- settings.onclick = function() {
- self.execCommand(settings.cmd);
- };
- }
- if (!settings.text && !settings.icon) {
- settings.icon = name;
- }
- self.buttons = self.buttons || {};
- settings.tooltip = settings.tooltip || settings.title;
- self.buttons[name] = settings;
- },
- /**
- * Adds a menu item to be used in the menus of the theme. There might be multiple instances
- * of this menu item for example it might be used in the main menus of the theme but also in
- * the context menu so make sure that it's self contained and supports multiple instances.
- *
- * @method addMenuItem
- * @param {String} name Menu item name to add.
- * @param {Object} settings Settings object with title, cmd etc.
- * @example
- * // Adds a custom menu item to the editor that inserts contents when clicked
- * // The context option allows you to add the menu item to an existing default menu
- * tinymce.init({
- * ...
- *
- * setup: function(ed) {
- * ed.addMenuItem('example', {
- * text: 'My menu item',
- * context: 'tools',
- * onclick: function() {
- * ed.insertContent('Hello world!!');
- * }
- * });
- * }
- * });
- */
- addMenuItem: function(name, settings) {
- var self = this;
- if (settings.cmd) {
- settings.onclick = function() {
- self.execCommand(settings.cmd);
- };
- }
- self.menuItems = self.menuItems || {};
- self.menuItems[name] = settings;
- },
- /**
- * Adds a custom command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with execCommand.
- *
- * @method addCommand
- * @param {String} name Command name to add/override.
- * @param {addCommandCallback} callback Function to execute when the command occurs.
- * @param {Object} scope Optional scope to execute the function in.
- * @example
- * // Adds a custom command that later can be executed using execCommand
- * tinymce.init({
- * ...
- *
- * setup: function(ed) {
- * // Register example command
- * ed.addCommand('mycommand', function(ui, v) {
- * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
- * });
- * }
- * });
- */
- addCommand: function(name, callback, scope) {
- /**
- * Callback function that gets called when a command is executed.
- *
- * @callback addCommandCallback
- * @param {Boolean} ui Display UI state true/false.
- * @param {Object} value Optional value for command.
- * @return {Boolean} True/false state if the command was handled or not.
- */
- this.execCommands[name] = {func: callback, scope: scope || this};
- },
- /**
- * Adds a custom query state command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with queryCommandState function.
- *
- * @method addQueryStateHandler
- * @param {String} name Command name to add/override.
- * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
- * @param {Object} scope Optional scope to execute the function in.
- */
- addQueryStateHandler: function(name, callback, scope) {
- /**
- * Callback function that gets called when a queryCommandState is executed.
- *
- * @callback addQueryStateHandlerCallback
- * @return {Boolean} True/false state if the command is enabled or not like is it bold.
- */
- this.queryStateCommands[name] = {func: callback, scope: scope || this};
- },
- /**
- * Adds a custom query value command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with queryCommandValue function.
- *
- * @method addQueryValueHandler
- * @param {String} name Command name to add/override.
- * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
- * @param {Object} scope Optional scope to execute the function in.
- */
- addQueryValueHandler: function(name, callback, scope) {
- /**
- * Callback function that gets called when a queryCommandValue is executed.
- *
- * @callback addQueryValueHandlerCallback
- * @return {Object} Value of the command or undefined.
- */
- this.queryValueCommands[name] = {func: callback, scope: scope || this};
- },
- /**
- * Adds a keyboard shortcut for some command or function.
- *
- * @method addShortcut
- * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
- * @param {String} desc Text description for the command.
- * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
- * @param {Object} sc Optional scope to execute the function in.
- * @return {Boolean} true/false state if the shortcut was added or not.
- */
- addShortcut: function(pattern, desc, cmdFunc, scope) {
- this.shortcuts.add(pattern, desc, cmdFunc, scope);
- },
- /**
- * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
- * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
- * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
- * return true it will handle the command as a internal browser command.
- *
- * @method execCommand
- * @param {String} cmd Command name to execute, for example mceLink or Bold.
- * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
- * @param {mixed} value Optional command value, this can be anything.
- * @param {Object} a Optional arguments object.
- */
- execCommand: function(cmd, ui, value, args) {
- var self = this, state = 0, cmdItem;
- if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
- self.focus();
- }
- args = extend({}, args);
- args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
- if (args.isDefaultPrevented()) {
- return false;
- }
- // Registred commands
- if ((cmdItem = self.execCommands[cmd])) {
- // Fall through on true
- if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
- self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
- return true;
- }
- }
- // Plugin commands
- each(self.plugins, function(p) {
- if (p.execCommand && p.execCommand(cmd, ui, value)) {
- self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
- state = true;
- return false;
- }
- });
- if (state) {
- return state;
- }
- // Theme commands
- if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
- self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
- return true;
- }
- // Editor commands
- if (self.editorCommands.execCommand(cmd, ui, value)) {
- self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
- return true;
- }
- // Browser commands
- self.getDoc().execCommand(cmd, ui, value);
- self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
- },
- /**
- * Returns a command specific state, for example if bold is enabled or not.
- *
- * @method queryCommandState
- * @param {string} cmd Command to query state from.
- * @return {Boolean} Command specific state, for example if bold is enabled or not.
- */
- queryCommandState: function(cmd) {
- var self = this, queryItem, returnVal;
- // Is hidden then return undefined
- if (self._isHidden()) {
- return;
- }
- // Registred commands
- if ((queryItem = self.queryStateCommands[cmd])) {
- returnVal = queryItem.func.call(queryItem.scope);
- // Fall though on true
- if (returnVal !== true) {
- return returnVal;
- }
- }
- // Editor commands
- returnVal = self.editorCommands.queryCommandState(cmd);
- if (returnVal !== -1) {
- return returnVal;
- }
- // Browser commands
- try {
- return self.getDoc().queryCommandState(cmd);
- } catch (ex) {
- // Fails sometimes see bug: 1896577
- }
- },
- /**
- * Returns a command specific value, for example the current font size.
- *
- * @method queryCommandValue
- * @param {string} cmd Command to query value from.
- * @return {Object} Command specific value, for example the current font size.
- */
- queryCommandValue: function(cmd) {
- var self = this, queryItem, returnVal;
- // Is hidden then return undefined
- if (self._isHidden()) {
- return;
- }
- // Registred commands
- if ((queryItem = self.queryValueCommands[cmd])) {
- returnVal = queryItem.func.call(queryItem.scope);
- // Fall though on true
- if (returnVal !== true) {
- return returnVal;
- }
- }
- // Editor commands
- returnVal = self.editorCommands.queryCommandValue(cmd);
- if (returnVal !== undefined) {
- return returnVal;
- }
- // Browser commands
- try {
- return self.getDoc().queryCommandValue(cmd);
- } catch (ex) {
- // Fails sometimes see bug: 1896577
- }
- },
- /**
- * Shows the editor and hides any textarea/div that the editor is supposed to replace.
- *
- * @method show
- */
- show: function() {
- var self = this;
- DOM.show(self.getContainer());
- DOM.hide(self.id);
- self.load();
- self.fire('show');
- },
- /**
- * Hides the editor and shows any textarea/div that the editor is supposed to replace.
- *
- * @method hide
- */
- hide: function() {
- var self = this, doc = self.getDoc();
- // Fixed bug where IE has a blinking cursor left from the editor
- if (ie && doc && !self.inline) {
- doc.execCommand('SelectAll');
- }
- // We must save before we hide so Safari doesn't crash
- self.save();
- // defer the call to hide to prevent an IE9 crash #4921
- DOM.hide(self.getContainer());
- DOM.setStyle(self.id, 'display', self.orgDisplay);
- self.fire('hide');
- },
- /**
- * Returns true/false if the editor is hidden or not.
- *
- * @method isHidden
- * @return {Boolean} True/false if the editor is hidden or not.
- */
- isHidden: function() {
- return !DOM.isHidden(this.id);
- },
- /**
- * Sets the progress state, this will display a throbber/progess for the editor.
- * This is ideal for asycronous operations like an AJAX save call.
- *
- * @method setProgressState
- * @param {Boolean} state Boolean state if the progress should be shown or hidden.
- * @param {Number} time Optional time to wait before the progress gets shown.
- * @return {Boolean} Same as the input state.
- * @example
- * // Show progress for the active editor
- * tinymce.activeEditor.setProgressState(true);
- *
- * // Hide progress for the active editor
- * tinymce.activeEditor.setProgressState(false);
- *
- * // Show progress after 3 seconds
- * tinymce.activeEditor.setProgressState(true, 3000);
- */
- setProgressState: function(state, time) {
- this.fire('ProgressState', {state: state, time: time});
- },
- /**
- * Loads contents from the textarea or div element that got converted into an editor instance.
- * This method will move the contents from that textarea or div into the editor by using setContent
- * so all events etc that method has will get dispatched as well.
- *
- * @method load
- * @param {Object} args Optional content object, this gets passed around through the whole load process.
- * @return {String} HTML string that got set into the editor.
- */
- load: function(args) {
- var self = this, elm = self.getElement(), html;
- if (elm) {
- args = args || {};
- args.load = true;
- html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
- args.element = elm;
- if (!args.no_events) {
- self.fire('LoadContent', args);
- }
- args.element = elm = null;
- return html;
- }
- },
- /**
- * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
- * This method will move the HTML contents from the editor into that textarea or div by getContent
- * so all events etc that method has will get dispatched as well.
- *
- * @method save
- * @param {Object} args Optional content object, this gets passed around through the whole save process.
- * @return {String} HTML string that got set into the textarea/div.
- */
- save: function(args) {
- var self = this, elm = self.getElement(), html, form;
- if (!elm || !self.initialized) {
- return;
- }
- args = args || {};
- args.save = true;
- args.element = elm;
- html = args.content = self.getContent(args);
- if (!args.no_events) {
- self.fire('SaveContent', args);
- }
- html = args.content;
- if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
- // Update DIV element when not in inline mode
- if (!self.inline) {
- elm.innerHTML = html;
- }
- // Update hidden form element
- if ((form = DOM.getParent(self.id, 'form'))) {
- each(form.elements, function(elm) {
- if (elm.name == self.id) {
- elm.value = html;
- return false;
- }
- });
- }
- } else {
- elm.value = html;
- }
- args.element = elm = null;
- if (args.set_dirty !== false) {
- self.isNotDirty = true;
- }
- return html;
- },
- /**
- * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
- * the different cleanup rules options.
- *
- * @method setContent
- * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
- * @param {Object} args Optional content object, this gets passed around through the whole set process.
- * @return {String} HTML string that got set into the editor.
- * @example
- * // Sets the HTML contents of the activeEditor editor
- * tinymce.activeEditor.setContent('<span>some</span> html');
- *
- * // Sets the raw contents of the activeEditor editor
- * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
- *
- * // Sets the content of a specific editor (my_editor in this example)
- * tinymce.get('my_editor').setContent(data);
- *
- * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
- * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
- */
- setContent: function(content, args) {
- var self = this, body = self.getBody(), forcedRootBlockName;
- // Setup args object
- args = args || {};
- args.format = args.format || 'html';
- args.set = true;
- args.content = content;
- // Do preprocessing
- if (!args.no_events) {
- self.fire('BeforeSetContent', args);
- }
- content = args.content;
- // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
- // It will also be impossible to place the caret in the editor unless there is a BR element present
- if (content.length === 0 || /^\s+$/.test(content)) {
- forcedRootBlockName = self.settings.forced_root_block;
- // Check if forcedRootBlock is configured and that the block is a valid child of the body
- if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
- // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
- content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
- content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
- } else if (!ie) {
- // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
- content = '<br data-mce-bogus="1">';
- }
- body.innerHTML = content;
- self.fire('SetContent', args);
- } else {
- // Parse and serialize the html
- if (args.format !== 'raw') {
- content = new Serializer({}, self.schema).serialize(
- self.parser.parse(content, {isRootContent: true})
- );
- }
- // Set the new cleaned contents to the editor
- args.content = trim(content);
- self.dom.setHTML(body, args.content);
- // Do post processing
- if (!args.no_events) {
- self.fire('SetContent', args);
- }
- // Don't normalize selection if the focused element isn't the body in
- // content editable mode since it will steal focus otherwise
- /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
- self.selection.normalize();
- }*/
- }
- return args.content;
- },
- /**
- * Gets the content from the editor instance, this will cleanup the content before it gets returned using
- * the different cleanup rules options.
- *
- * @method getContent
- * @param {Object} args Optional content object, this gets passed around through the whole get process.
- * @return {String} Cleaned content string, normally HTML contents.
- * @example
- * // Get the HTML contents of the currently active editor
- * console.debug(tinymce.activeEditor.getContent());
- *
- * // Get the raw contents of the currently active editor
- * tinymce.activeEditor.getContent({format: 'raw'});
- *
- * // Get content of a specific editor:
- * tinymce.get('content id').getContent()
- */
- getContent: function(args) {
- var self = this, content, body = self.getBody();
- // Setup args object
- args = args || {};
- args.format = args.format || 'html';
- args.get = true;
- args.getInner = true;
- // Do preprocessing
- if (!args.no_events) {
- self.fire('BeforeGetContent', args);
- }
- // Get raw contents or by default the cleaned contents
- if (args.format == 'raw') {
- content = body.innerHTML;
- } else if (args.format == 'text') {
- content = body.innerText || body.textContent;
- } else {
- content = self.serializer.serialize(body, args);
- }
- // Trim whitespace in beginning/end of HTML
- if (args.format != 'text') {
- args.content = trim(content);
- } else {
- args.content = content;
- }
- // Do post processing
- if (!args.no_events) {
- self.fire('GetContent', args);
- }
- return args.content;
- },
- /**
- * Inserts content at caret position.
- *
- * @method insertContent
- * @param {String} content Content to insert.
- */
- insertContent: function(content) {
- this.execCommand('mceInsertContent', false, content);
- },
- /**
- * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
- *
- * @method isDirty
- * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
- * @example
- * if (tinymce.activeEditor.isDirty())
- * alert("You must save your contents.");
- */
- isDirty: function() {
- return !this.isNotDirty;
- },
- /**
- * Returns the editors container element. The container element wrappes in
- * all the elements added to the page for the editor. Such as UI, iframe etc.
- *
- * @method getContainer
- * @return {Element} HTML DOM element for the editor container.
- */
- getContainer: function() {
- var self = this;
- if (!self.container) {
- self.container = DOM.get(self.editorContainer || self.id + '_parent');
- }
- return self.container;
- },
- /**
- * Returns the editors content area container element. The this element is the one who
- * holds the iframe or the editable element.
- *
- * @method getContentAreaContainer
- * @return {Element} HTML DOM element for the editor area container.
- */
- getContentAreaContainer: function() {
- return this.contentAreaContainer;
- },
- /**
- * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
- *
- * @method getElement
- * @return {Element} HTML DOM element for the replaced element.
- */
- getElement: function() {
- return DOM.get(this.settings.content_element || this.id);
- },
- /**
- * Returns the iframes window object.
- *
- * @method getWin
- * @return {Window} Iframe DOM window object.
- */
- getWin: function() {
- var self = this, elm;
- if (!self.contentWindow) {
- elm = DOM.get(self.id + "_ifr");
- if (elm) {
- self.contentWindow = elm.contentWindow;
- }
- }
- return self.contentWindow;
- },
- /**
- * Returns the iframes document object.
- *
- * @method getDoc
- * @return {Document} Iframe DOM document object.
- */
- getDoc: function() {
- var self = this, win;
- if (!self.contentDocument) {
- win = self.getWin();
- if (win) {
- self.contentDocument = win.document;
- }
- }
- return self.contentDocument;
- },
- /**
- * Returns the iframes body element.
- *
- * @method getBody
- * @return {Element} Iframe body element.
- */
- getBody: function() {
- return this.bodyElement || this.getDoc().body;
- },
- /**
- * URL converter function this gets executed each time a user adds an img, a or
- * any other element that has a URL in it. This will be called both by the DOM and HTML
- * manipulation functions.
- *
- * @method convertURL
- * @param {string} url URL to convert.
- * @param {string} name Attribute name src, href etc.
- * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
- * @return {string} Converted URL string.
- */
- convertURL: function(url, name, elm) {
- var self = this, settings = self.settings;
- // Use callback instead
- if (settings.urlconverter_callback) {
- return self.execCallback('urlconverter_callback', url, elm, true, name);
- }
- // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
- if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
- return url;
- }
- // Convert to relative
- if (settings.relative_urls) {
- return self.documentBaseURI.toRelative(url);
- }
- // Convert to absolute
- url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
- return url;
- },
- /**
- * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
- *
- * @method addVisual
- * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
- */
- addVisual: function(elm) {
- var self = this, settings = self.settings, dom = self.dom, cls;
- elm = elm || self.getBody();
- if (self.hasVisual === undefined) {
- self.hasVisual = settings.visual;
- }
- each(dom.select('table,a', elm), function(elm) {
- var value;
- switch (elm.nodeName) {
- case 'TABLE':
- cls = settings.visual_table_class || 'mce-item-table';
- value = dom.getAttrib(elm, 'border');
- if (!value || value == '0') {
- if (self.hasVisual) {
- dom.addClass(elm, cls);
- } else {
- dom.removeClass(elm, cls);
- }
- }
- return;
- case 'A':
- if (!dom.getAttrib(elm, 'href', false)) {
- value = dom.getAttrib(elm, 'name') || elm.id;
- cls = settings.visual_anchor_class || 'mce-item-anchor';
- if (value) {
- if (self.hasVisual) {
- dom.addClass(elm, cls);
- } else {
- dom.removeClass(elm, cls);
- }
- }
- }
- return;
- }
- });
- self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
- },
- /**
- * Removes the editor from the dom and tinymce collection.
- *
- * @method remove
- */
- remove: function() {
- var self = this;
- if (!self.removed) {
- self.removed = 1;
- self.save();
- // Remove any hidden input
- if (self.hasHiddenInput) {
- DOM.remove(self.getElement().nextSibling);
- }
- if (!self.inline) {
- // IE 9 has a bug where the selection stops working if you place the
- // caret inside the editor then remove the iframe
- if (ie && ie < 10) {
- self.getDoc().execCommand('SelectAll', false, null);
- }
- DOM.setStyle(self.id, 'display', self.orgDisplay);
- self.getBody().onload = null; // Prevent #6816
- // Don't clear the window or document if content editable
- // is enabled since other instances might still be present
- Event.unbind(self.getWin());
- Event.unbind(self.getDoc());
- }
- var elm = self.getContainer();
- Event.unbind(self.getBody());
- Event.unbind(elm);
- self.fire('remove');
- self.editorManager.remove(self);
- DOM.remove(elm);
- self.destroy();
- }
- },
- bindNative: function(name) {
- var self = this;
- if (self.settings.readonly) {
- return;
- }
- if (self.initialized) {
- self.dom.bind(getEventTarget(self, name), name, function(e) {
- self.fire(name, e);
- });
- } else {
- if (!self._pendingNativeEvents) {
- self._pendingNativeEvents = [name];
- } else {
- self._pendingNativeEvents.push(name);
- }
- }
- },
- unbindNative: function(name) {
- var self = this;
- if (self.initialized) {
- self.dom.unbind(name);
- }
- },
- /**
- * Destroys the editor instance by removing all events, element references or other resources
- * that could leak memory. This method will be called automatically when the page is unloaded
- * but you can also call it directly if you know what you are doing.
- *
- * @method destroy
- * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
- */
- destroy: function(automatic) {
- var self = this, form;
- // One time is enough
- if (self.destroyed) {
- return;
- }
- // If user manually calls destroy and not remove
- // Users seems to have logic that calls destroy instead of remove
- if (!automatic && !self.removed) {
- self.remove();
- return;
- }
- // We must unbind on Gecko since it would otherwise produce the pesky "attempt
- // to run compile-and-go script on a cleared scope" message
- if (automatic && isGecko) {
- Event.unbind(self.getDoc());
- Event.unbind(self.getWin());
- Event.unbind(self.getBody());
- }
- if (!automatic) {
- self.editorManager.off('beforeunload', self._beforeUnload);
- // Manual destroy
- if (self.theme && self.theme.destroy) {
- self.theme.destroy();
- }
- // Destroy controls, selection and dom
- self.selection.destroy();
- self.dom.destroy();
- }
- form = self.formElement;
- if (form) {
- if (form._mceOldSubmit) {
- form.submit = form._mceOldSubmit;
- form._mceOldSubmit = null;
- }
- DOM.unbind(form, 'submit reset', self.formEventDelegate);
- }
- self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
- self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null;
- if (self.selection) {
- self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
- }
- self.destroyed = 1;
- },
- // Internal functions
- _refreshContentEditable: function() {
- var self = this, body, parent;
- // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
- if (self._isHidden()) {
- body = self.getBody();
- parent = body.parentNode;
- parent.removeChild(body);
- parent.appendChild(body);
- body.focus();
- }
- },
- _isHidden: function() {
- var sel;
- if (!isGecko) {
- return 0;
- }
- // Weird, wheres that cursor selection?
- sel = this.selection.getSel();
- return (!sel || !sel.rangeCount || sel.rangeCount === 0);
- }
- };
- extend(Editor.prototype, Observable);
- return Editor;
- });
|