Editor.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit.Editor"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.Editor"] = true;
  8. dojo.provide("dijit.Editor");
  9. dojo.require("dijit._editor.RichText");
  10. dojo.require("dijit.Toolbar");
  11. dojo.require("dijit.ToolbarSeparator");
  12. dojo.require("dijit._editor._Plugin");
  13. dojo.require("dijit._editor.plugins.EnterKeyHandling");
  14. dojo.require("dijit._editor.range");
  15. dojo.require("dijit._Container");
  16. dojo.require("dojo.i18n");
  17. dojo.require("dijit.layout._LayoutWidget");
  18. dojo.requireLocalization("dijit._editor", "commands", null, "ROOT,ar,az,bg,ca,cs,da,de,el,es,fi,fr,he,hr,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
  19. dojo.declare(
  20. "dijit.Editor",
  21. dijit._editor.RichText,
  22. {
  23. // summary:
  24. // A rich text Editing widget
  25. //
  26. // description:
  27. // This widget provides basic WYSIWYG editing features, based on the browser's
  28. // underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`).
  29. // A plugin model is available to extend the editor's capabilities as well as the
  30. // the options available in the toolbar. Content generation may vary across
  31. // browsers, and clipboard operations may have different results, to name
  32. // a few limitations. Note: this widget should not be used with the HTML
  33. // <TEXTAREA> tag -- see dijit._editor.RichText for details.
  34. // plugins: [const] Object[]
  35. // A list of plugin names (as strings) or instances (as objects)
  36. // for this widget.
  37. //
  38. // When declared in markup, it might look like:
  39. // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
  40. plugins: null,
  41. // extraPlugins: [const] Object[]
  42. // A list of extra plugin names which will be appended to plugins array
  43. extraPlugins: null,
  44. constructor: function(){
  45. // summary:
  46. // Runs on widget initialization to setup arrays etc.
  47. // tags:
  48. // private
  49. if(!dojo.isArray(this.plugins)){
  50. this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
  51. "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull",
  52. "dijit._editor.plugins.EnterKeyHandling" /*, "createLink"*/];
  53. }
  54. this._plugins=[];
  55. this._editInterval = this.editActionInterval * 1000;
  56. //IE will always lose focus when other element gets focus, while for FF and safari,
  57. //when no iframe is used, focus will be lost whenever another element gets focus.
  58. //For IE, we can connect to onBeforeDeactivate, which will be called right before
  59. //the focus is lost, so we can obtain the selected range. For other browsers,
  60. //no equivelent of onBeforeDeactivate, so we need to do two things to make sure
  61. //selection is properly saved before focus is lost: 1) when user clicks another
  62. //element in the page, in which case we listen to mousedown on the entire page and
  63. //see whether user clicks out of a focus editor, if so, save selection (focus will
  64. //only lost after onmousedown event is fired, so we can obtain correct caret pos.)
  65. //2) when user tabs away from the editor, which is handled in onKeyDown below.
  66. if(dojo.isIE){
  67. this.events.push("onBeforeDeactivate");
  68. this.events.push("onBeforeActivate");
  69. }
  70. },
  71. postMixInProperties: function() {
  72. // summary:
  73. // Extension to make sure a deferred is in place before certain functions
  74. // execute, like making sure all the plugins are properly inserted.
  75. // Set up a deferred so that the value isn't applied to the editor
  76. // until all the plugins load, needed to avoid timing condition
  77. // reported in #10537.
  78. this.setValueDeferred = new dojo.Deferred();
  79. this.inherited(arguments);
  80. },
  81. postCreate: function(){
  82. //for custom undo/redo, if enabled.
  83. this._steps=this._steps.slice(0);
  84. this._undoedSteps=this._undoedSteps.slice(0);
  85. if(dojo.isArray(this.extraPlugins)){
  86. this.plugins=this.plugins.concat(this.extraPlugins);
  87. }
  88. this.inherited(arguments);
  89. this.commands = dojo.i18n.getLocalization("dijit._editor", "commands", this.lang);
  90. if(!this.toolbar){
  91. // if we haven't been assigned a toolbar, create one
  92. this.toolbar = new dijit.Toolbar({
  93. dir: this.dir,
  94. lang: this.lang
  95. });
  96. this.header.appendChild(this.toolbar.domNode);
  97. }
  98. dojo.forEach(this.plugins, this.addPlugin, this);
  99. // Okay, denote the value can now be set.
  100. this.setValueDeferred.callback(true);
  101. dojo.addClass(this.iframe.parentNode, "dijitEditorIFrameContainer");
  102. dojo.addClass(this.iframe, "dijitEditorIFrame");
  103. dojo.attr(this.iframe, "allowTransparency", true);
  104. if(dojo.isWebKit){
  105. // Disable selecting the entire editor by inadvertant double-clicks.
  106. // on buttons, title bar, etc. Otherwise clicking too fast on
  107. // a button such as undo/redo selects the entire editor.
  108. dojo.style(this.domNode, "KhtmlUserSelect", "none");
  109. }
  110. this.toolbar.startup();
  111. this.onNormalizedDisplayChanged(); //update toolbar button status
  112. },
  113. destroy: function(){
  114. dojo.forEach(this._plugins, function(p){
  115. if(p && p.destroy){
  116. p.destroy();
  117. }
  118. });
  119. this._plugins=[];
  120. this.toolbar.destroyRecursive();
  121. delete this.toolbar;
  122. this.inherited(arguments);
  123. },
  124. addPlugin: function(/*String||Object*/plugin, /*Integer?*/index){
  125. // summary:
  126. // takes a plugin name as a string or a plugin instance and
  127. // adds it to the toolbar and associates it with this editor
  128. // instance. The resulting plugin is added to the Editor's
  129. // plugins array. If index is passed, it's placed in the plugins
  130. // array at that index. No big magic, but a nice helper for
  131. // passing in plugin names via markup.
  132. //
  133. // plugin: String, args object or plugin instance
  134. //
  135. // args:
  136. // This object will be passed to the plugin constructor
  137. //
  138. // index: Integer
  139. // Used when creating an instance from
  140. // something already in this.plugins. Ensures that the new
  141. // instance is assigned to this.plugins at that index.
  142. var args=dojo.isString(plugin)?{name:plugin}:plugin;
  143. if(!args.setEditor){
  144. var o={"args":args,"plugin":null,"editor":this};
  145. dojo.publish(dijit._scopeName + ".Editor.getPlugin",[o]);
  146. if(!o.plugin){
  147. var pc = dojo.getObject(args.name);
  148. if(pc){
  149. o.plugin=new pc(args);
  150. }
  151. }
  152. if(!o.plugin){
  153. console.warn('Cannot find plugin',plugin);
  154. return;
  155. }
  156. plugin=o.plugin;
  157. }
  158. if(arguments.length > 1){
  159. this._plugins[index] = plugin;
  160. }else{
  161. this._plugins.push(plugin);
  162. }
  163. plugin.setEditor(this);
  164. if(dojo.isFunction(plugin.setToolbar)){
  165. plugin.setToolbar(this.toolbar);
  166. }
  167. },
  168. //the following 3 functions are required to make the editor play nice under a layout widget, see #4070
  169. startup: function(){
  170. // summary:
  171. // Exists to make Editor work as a child of a layout widget.
  172. // Developers don't need to call this method.
  173. // tags:
  174. // protected
  175. //console.log('startup',arguments);
  176. },
  177. resize: function(size){
  178. // summary:
  179. // Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize`
  180. if(size){
  181. // we've been given a height/width for the entire editor (toolbar + contents), calls layout()
  182. // to split the allocated size between the toolbar and the contents
  183. dijit.layout._LayoutWidget.prototype.resize.apply(this, arguments);
  184. }
  185. /*
  186. else{
  187. // do nothing, the editor is already laid out correctly. The user has probably specified
  188. // the height parameter, which was used to set a size on the iframe
  189. }
  190. */
  191. },
  192. layout: function(){
  193. // summary:
  194. // Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly
  195. // tags:
  196. // protected
  197. // Converts the iframe (or rather the <div> surrounding it) to take all the available space
  198. // except what's needed for the header (toolbars) and footer (breadcrumbs, etc).
  199. // A class was added to the iframe container and some themes style it, so we have to
  200. // calc off the added margins and padding too. See tracker: #10662
  201. var areaHeight = (this._contentBox.h -
  202. (this.getHeaderHeight() + this.getFooterHeight() +
  203. dojo._getPadBorderExtents(this.iframe.parentNode).h +
  204. dojo._getMarginExtents(this.iframe.parentNode).h));
  205. this.editingArea.style.height = areaHeight + "px";
  206. if(this.iframe){
  207. this.iframe.style.height="100%";
  208. }
  209. this._layoutMode = true;
  210. },
  211. _onIEMouseDown: function(/*Event*/ e){
  212. // summary:
  213. // IE only to prevent 2 clicks to focus
  214. // tags:
  215. // private
  216. var outsideClientArea;
  217. // IE 8's componentFromPoint is broken, which is a shame since it
  218. // was smaller code, but oh well. We have to do this brute force
  219. // to detect if the click was scroller or not.
  220. var b = this.document.body;
  221. var clientWidth = b.clientWidth;
  222. var clientHeight = b.clientHeight;
  223. var clientLeft = b.clientLeft;
  224. var offsetWidth = b.offsetWidth;
  225. var offsetHeight = b.offsetHeight;
  226. var offsetLeft = b.offsetLeft;
  227. //Check for vertical scroller click.
  228. bodyDir = b.dir ? b.dir.toLowerCase() : "";
  229. if(bodyDir != "rtl"){
  230. if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){
  231. // Check the click was between width and offset width, if so, scroller
  232. outsideClientArea = true;
  233. }
  234. }else{
  235. // RTL mode, we have to go by the left offsets.
  236. if(e.x < clientLeft && e.x > offsetLeft){
  237. // Check the click was between width and offset width, if so, scroller
  238. outsideClientArea = true;
  239. }
  240. }
  241. if(!outsideClientArea){
  242. // Okay, might be horiz scroller, check that.
  243. if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){
  244. // Horizontal scroller.
  245. outsideClientArea = true;
  246. }
  247. }
  248. if(!outsideClientArea){
  249. delete this._cursorToStart; // Remove the force to cursor to start position.
  250. delete this._savedSelection; // new mouse position overrides old selection
  251. if(e.target.tagName == "BODY"){
  252. setTimeout(dojo.hitch(this, "placeCursorAtEnd"), 0);
  253. }
  254. this.inherited(arguments);
  255. }
  256. },
  257. onBeforeActivate: function(e){
  258. this._restoreSelection();
  259. },
  260. onBeforeDeactivate: function(e){
  261. // summary:
  262. // Called on IE right before focus is lost. Saves the selected range.
  263. // tags:
  264. // private
  265. if(this.customUndo){
  266. this.endEditing(true);
  267. }
  268. //in IE, the selection will be lost when other elements get focus,
  269. //let's save focus before the editor is deactivated
  270. if(e.target.tagName != "BODY"){
  271. this._saveSelection();
  272. }
  273. //console.log('onBeforeDeactivate',this);
  274. },
  275. /* beginning of custom undo/redo support */
  276. // customUndo: Boolean
  277. // Whether we shall use custom undo/redo support instead of the native
  278. // browser support. By default, we now use custom undo. It works better
  279. // than native browser support and provides a consistent behavior across
  280. // browsers with a minimal performance hit. We already had the hit on
  281. // the slowest browser, IE, anyway.
  282. customUndo: true,
  283. // editActionInterval: Integer
  284. // When using customUndo, not every keystroke will be saved as a step.
  285. // Instead typing (including delete) will be grouped together: after
  286. // a user stops typing for editActionInterval seconds, a step will be
  287. // saved; if a user resume typing within editActionInterval seconds,
  288. // the timeout will be restarted. By default, editActionInterval is 3
  289. // seconds.
  290. editActionInterval: 3,
  291. beginEditing: function(cmd){
  292. // summary:
  293. // Called to note that the user has started typing alphanumeric characters, if it's not already noted.
  294. // Deals with saving undo; see editActionInterval parameter.
  295. // tags:
  296. // private
  297. if(!this._inEditing){
  298. this._inEditing=true;
  299. this._beginEditing(cmd);
  300. }
  301. if(this.editActionInterval>0){
  302. if(this._editTimer){
  303. clearTimeout(this._editTimer);
  304. }
  305. this._editTimer = setTimeout(dojo.hitch(this, this.endEditing), this._editInterval);
  306. }
  307. },
  308. // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
  309. _steps:[],
  310. _undoedSteps:[],
  311. execCommand: function(cmd){
  312. // summary:
  313. // Main handler for executing any commands to the editor, like paste, bold, etc.
  314. // Called by plugins, but not meant to be called by end users.
  315. // tags:
  316. // protected
  317. if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
  318. return this[cmd]();
  319. }else{
  320. if(this.customUndo){
  321. this.endEditing();
  322. this._beginEditing();
  323. }
  324. var r;
  325. var isClipboard = /copy|cut|paste/.test(cmd);
  326. try{
  327. r = this.inherited(arguments);
  328. if(dojo.isWebKit && isClipboard && !r){ //see #4598: webkit does not guarantee clipboard support from js
  329. throw { code: 1011 }; // throw an object like Mozilla's error
  330. }
  331. }catch(e){
  332. //TODO: when else might we get an exception? Do we need the Mozilla test below?
  333. if(e.code == 1011 /* Mozilla: service denied */ && isClipboard){
  334. // Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136
  335. var sub = dojo.string.substitute,
  336. accel = {cut:'X', copy:'C', paste:'V'};
  337. alert(sub(this.commands.systemShortcut,
  338. [this.commands[cmd], sub(this.commands[dojo.isMac ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
  339. }
  340. r = false;
  341. }
  342. if(this.customUndo){
  343. this._endEditing();
  344. }
  345. return r;
  346. }
  347. },
  348. queryCommandEnabled: function(cmd){
  349. // summary:
  350. // Returns true if specified editor command is enabled.
  351. // Used by the plugins to know when to highlight/not highlight buttons.
  352. // tags:
  353. // protected
  354. if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
  355. return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0);
  356. }else{
  357. return this.inherited(arguments);
  358. }
  359. },
  360. _moveToBookmark: function(b){
  361. // summary:
  362. // Selects the text specified in bookmark b
  363. // tags:
  364. // private
  365. var bookmark = b.mark;
  366. var mark = b.mark;
  367. var col = b.isCollapsed;
  368. var r, sNode, eNode, sel;
  369. if(mark){
  370. if(dojo.isIE < 9){
  371. if(dojo.isArray(mark)){
  372. //IE CONTROL, have to use the native bookmark.
  373. bookmark = [];
  374. dojo.forEach(mark,function(n){
  375. bookmark.push(dijit.range.getNode(n,this.editNode));
  376. },this);
  377. dojo.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]);
  378. }else{
  379. if(mark.startContainer && mark.endContainer){
  380. // Use the pseudo WC3 range API. This works better for positions
  381. // than the IE native bookmark code.
  382. sel = dijit.range.getSelection(this.window);
  383. if(sel && sel.removeAllRanges){
  384. sel.removeAllRanges();
  385. r = dijit.range.create(this.window);
  386. sNode = dijit.range.getNode(mark.startContainer,this.editNode);
  387. eNode = dijit.range.getNode(mark.endContainer,this.editNode);
  388. if(sNode && eNode){
  389. // Okay, we believe we found the position, so add it into the selection
  390. // There are cases where it may not be found, particularly in undo/redo, when
  391. // IE changes the underlying DOM on us (wraps text in a <p> tag or similar.
  392. // So, in those cases, don't bother restoring selection.
  393. r.setStart(sNode,mark.startOffset);
  394. r.setEnd(eNode,mark.endOffset);
  395. sel.addRange(r);
  396. }
  397. }
  398. }
  399. }
  400. }else{//w3c range
  401. sel = dijit.range.getSelection(this.window);
  402. if(sel && sel.removeAllRanges){
  403. sel.removeAllRanges();
  404. r = dijit.range.create(this.window);
  405. sNode = dijit.range.getNode(mark.startContainer,this.editNode);
  406. eNode = dijit.range.getNode(mark.endContainer,this.editNode);
  407. if(sNode && eNode){
  408. // Okay, we believe we found the position, so add it into the selection
  409. // There are cases where it may not be found, particularly in undo/redo, when
  410. // formatting as been done and so on, so don't restore selection then.
  411. r.setStart(sNode,mark.startOffset);
  412. r.setEnd(eNode,mark.endOffset);
  413. sel.addRange(r);
  414. }
  415. }
  416. }
  417. }
  418. },
  419. _changeToStep: function(from, to){
  420. // summary:
  421. // Reverts editor to "to" setting, from the undo stack.
  422. // tags:
  423. // private
  424. this.setValue(to.text);
  425. var b=to.bookmark;
  426. if(!b){ return; }
  427. this._moveToBookmark(b);
  428. },
  429. undo: function(){
  430. // summary:
  431. // Handler for editor undo (ex: ctrl-z) operation
  432. // tags:
  433. // private
  434. //console.log('undo');
  435. var ret = false;
  436. if(!this._undoRedoActive){
  437. this._undoRedoActive = true;
  438. this.endEditing(true);
  439. var s=this._steps.pop();
  440. if(s && this._steps.length>0){
  441. this.focus();
  442. this._changeToStep(s,this._steps[this._steps.length-1]);
  443. this._undoedSteps.push(s);
  444. this.onDisplayChanged();
  445. delete this._undoRedoActive;
  446. ret = true;
  447. }
  448. delete this._undoRedoActive;
  449. }
  450. return ret;
  451. },
  452. redo: function(){
  453. // summary:
  454. // Handler for editor redo (ex: ctrl-y) operation
  455. // tags:
  456. // private
  457. //console.log('redo');
  458. var ret = false;
  459. if(!this._undoRedoActive){
  460. this._undoRedoActive = true;
  461. this.endEditing(true);
  462. var s=this._undoedSteps.pop();
  463. if(s && this._steps.length>0){
  464. this.focus();
  465. this._changeToStep(this._steps[this._steps.length-1],s);
  466. this._steps.push(s);
  467. this.onDisplayChanged();
  468. ret = true;
  469. }
  470. delete this._undoRedoActive;
  471. }
  472. return ret;
  473. },
  474. endEditing: function(ignore_caret){
  475. // summary:
  476. // Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
  477. // Deals with saving undo; see editActionInterval parameter.
  478. // tags:
  479. // private
  480. if(this._editTimer){
  481. clearTimeout(this._editTimer);
  482. }
  483. if(this._inEditing){
  484. this._endEditing(ignore_caret);
  485. this._inEditing=false;
  486. }
  487. },
  488. _getBookmark: function(){
  489. // summary:
  490. // Get the currently selected text
  491. // tags:
  492. // protected
  493. var b=dojo.withGlobal(this.window,dijit.getBookmark);
  494. var tmp=[];
  495. if(b && b.mark){
  496. var mark = b.mark;
  497. if(dojo.isIE < 9){
  498. // Try to use the pseudo range API on IE for better accuracy.
  499. var sel = dijit.range.getSelection(this.window);
  500. if(!dojo.isArray(mark)){
  501. if(sel){
  502. var range;
  503. if(sel.rangeCount){
  504. range = sel.getRangeAt(0);
  505. }
  506. if(range){
  507. b.mark = range.cloneRange();
  508. }else{
  509. b.mark = dojo.withGlobal(this.window,dijit.getBookmark);
  510. }
  511. }
  512. }else{
  513. // Control ranges (img, table, etc), handle differently.
  514. dojo.forEach(b.mark,function(n){
  515. tmp.push(dijit.range.getIndex(n,this.editNode).o);
  516. },this);
  517. b.mark = tmp;
  518. }
  519. }
  520. try{
  521. if(b.mark && b.mark.startContainer){
  522. tmp=dijit.range.getIndex(b.mark.startContainer,this.editNode).o;
  523. b.mark={startContainer:tmp,
  524. startOffset:b.mark.startOffset,
  525. endContainer:b.mark.endContainer===b.mark.startContainer?tmp:dijit.range.getIndex(b.mark.endContainer,this.editNode).o,
  526. endOffset:b.mark.endOffset};
  527. }
  528. }catch(e){
  529. b.mark = null;
  530. }
  531. }
  532. return b;
  533. },
  534. _beginEditing: function(cmd){
  535. // summary:
  536. // Called when the user starts typing alphanumeric characters.
  537. // Deals with saving undo; see editActionInterval parameter.
  538. // tags:
  539. // private
  540. if(this._steps.length === 0){
  541. // You want to use the editor content without post filtering
  542. // to make sure selection restores right for the 'initial' state.
  543. // and undo is called. So not using this.value, as it was 'processed'
  544. // and the line-up for selections may have been altered.
  545. this._steps.push({'text':dijit._editor.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()});
  546. }
  547. },
  548. _endEditing: function(ignore_caret){
  549. // summary:
  550. // Called when the user stops typing alphanumeric characters.
  551. // Deals with saving undo; see editActionInterval parameter.
  552. // tags:
  553. // private
  554. // Avoid filtering to make sure selections restore.
  555. var v = dijit._editor.getChildrenHtml(this.editNode);
  556. this._undoedSteps=[];//clear undoed steps
  557. this._steps.push({text: v, bookmark: this._getBookmark()});
  558. },
  559. onKeyDown: function(e){
  560. // summary:
  561. // Handler for onkeydown event.
  562. // tags:
  563. // private
  564. //We need to save selection if the user TAB away from this editor
  565. //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
  566. if(!dojo.isIE && !this.iframe && e.keyCode == dojo.keys.TAB && !this.tabIndent){
  567. this._saveSelection();
  568. }
  569. if(!this.customUndo){
  570. this.inherited(arguments);
  571. return;
  572. }
  573. var k = e.keyCode, ks = dojo.keys;
  574. if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
  575. if(k == 90 || k == 122){ //z
  576. dojo.stopEvent(e);
  577. this.undo();
  578. return;
  579. }else if(k == 89 || k == 121){ //y
  580. dojo.stopEvent(e);
  581. this.redo();
  582. return;
  583. }
  584. }
  585. this.inherited(arguments);
  586. switch(k){
  587. case ks.ENTER:
  588. case ks.BACKSPACE:
  589. case ks.DELETE:
  590. this.beginEditing();
  591. break;
  592. case 88: //x
  593. case 86: //v
  594. if(e.ctrlKey && !e.altKey && !e.metaKey){
  595. this.endEditing();//end current typing step if any
  596. if(e.keyCode == 88){
  597. this.beginEditing('cut');
  598. //use timeout to trigger after the cut is complete
  599. setTimeout(dojo.hitch(this, this.endEditing), 1);
  600. }else{
  601. this.beginEditing('paste');
  602. //use timeout to trigger after the paste is complete
  603. setTimeout(dojo.hitch(this, this.endEditing), 1);
  604. }
  605. break;
  606. }
  607. //pass through
  608. default:
  609. if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<dojo.keys.F1 || e.keyCode>dojo.keys.F15)){
  610. this.beginEditing();
  611. break;
  612. }
  613. //pass through
  614. case ks.ALT:
  615. this.endEditing();
  616. break;
  617. case ks.UP_ARROW:
  618. case ks.DOWN_ARROW:
  619. case ks.LEFT_ARROW:
  620. case ks.RIGHT_ARROW:
  621. case ks.HOME:
  622. case ks.END:
  623. case ks.PAGE_UP:
  624. case ks.PAGE_DOWN:
  625. this.endEditing(true);
  626. break;
  627. //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
  628. case ks.CTRL:
  629. case ks.SHIFT:
  630. case ks.TAB:
  631. break;
  632. }
  633. },
  634. _onBlur: function(){
  635. // summary:
  636. // Called from focus manager when focus has moved away from this editor
  637. // tags:
  638. // protected
  639. //this._saveSelection();
  640. this.inherited(arguments);
  641. this.endEditing(true);
  642. },
  643. _saveSelection: function(){
  644. // summary:
  645. // Save the currently selected text in _savedSelection attribute
  646. // tags:
  647. // private
  648. try{
  649. this._savedSelection=this._getBookmark();
  650. }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaniously. */}
  651. },
  652. _restoreSelection: function(){
  653. // summary:
  654. // Re-select the text specified in _savedSelection attribute;
  655. // see _saveSelection().
  656. // tags:
  657. // private
  658. if(this._savedSelection){
  659. // Clear off cursor to start, we're deliberately going to a selection.
  660. delete this._cursorToStart;
  661. // only restore the selection if the current range is collapsed
  662. // if not collapsed, then it means the editor does not lose
  663. // selection and there is no need to restore it
  664. if(dojo.withGlobal(this.window,'isCollapsed',dijit)){
  665. this._moveToBookmark(this._savedSelection);
  666. }
  667. delete this._savedSelection;
  668. }
  669. },
  670. onClick: function(){
  671. // summary:
  672. // Handler for when editor is clicked
  673. // tags:
  674. // protected
  675. this.endEditing(true);
  676. this.inherited(arguments);
  677. },
  678. replaceValue: function(/*String*/ html){
  679. // summary:
  680. // over-ride of replaceValue to support custom undo and stack maintainence.
  681. // tags:
  682. // protected
  683. if(!this.customUndo){
  684. this.inherited(arguments);
  685. }else{
  686. if(this.isClosed){
  687. this.setValue(html);
  688. }else{
  689. this.beginEditing();
  690. if(!html){
  691. html = "&nbsp;"
  692. }
  693. this.setValue(html);
  694. this.endEditing();
  695. }
  696. }
  697. },
  698. _setDisabledAttr: function(/*Boolean*/ value){
  699. var disableFunc = dojo.hitch(this, function(){
  700. if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){
  701. // Disable editor: disable all enabled buttons and remember that list
  702. dojo.forEach(this._plugins, function(p){
  703. p.set("disabled", true);
  704. });
  705. }else if(this.disabled && !value){
  706. // Restore plugins to being active.
  707. dojo.forEach(this._plugins, function(p){
  708. p.set("disabled", false);
  709. });
  710. }
  711. });
  712. this.setValueDeferred.addCallback(disableFunc);
  713. this.inherited(arguments);
  714. },
  715. _setStateClass: function(){
  716. try{
  717. this.inherited(arguments);
  718. // Let theme set the editor's text color based on editor enabled/disabled state.
  719. // We need to jump through hoops because the main document (where the theme CSS is)
  720. // is separate from the iframe's document.
  721. if(this.document && this.document.body){
  722. dojo.style(this.document.body, "color", dojo.style(this.iframe, "color"));
  723. }
  724. }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */}
  725. }
  726. }
  727. );
  728. // Register the "default plugins", ie, the built-in editor commands
  729. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
  730. if(o.plugin){ return; }
  731. var args = o.args, p;
  732. var _p = dijit._editor._Plugin;
  733. var name = args.name;
  734. switch(name){
  735. case "undo": case "redo": case "cut": case "copy": case "paste": case "insertOrderedList":
  736. case "insertUnorderedList": case "indent": case "outdent": case "justifyCenter":
  737. case "justifyFull": case "justifyLeft": case "justifyRight": case "delete":
  738. case "selectAll": case "removeFormat": case "unlink":
  739. case "insertHorizontalRule":
  740. p = new _p({ command: name });
  741. break;
  742. case "bold": case "italic": case "underline": case "strikethrough":
  743. case "subscript": case "superscript":
  744. p = new _p({ buttonClass: dijit.form.ToggleButton, command: name });
  745. break;
  746. case "|":
  747. p = new _p({ button: new dijit.ToolbarSeparator(), setEditor: function(editor) {this.editor = editor;} });
  748. }
  749. // console.log('name',name,p);
  750. o.plugin=p;
  751. });
  752. }