RichText.js 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662
  1. if (!dojo._hasResource["dijit._editor.RichText"]) { // _hasResource checks added
  2. // by build. Do not use
  3. // _hasResource directly in
  4. // your code.
  5. dojo._hasResource["dijit._editor.RichText"] = true;
  6. dojo.provide("dijit._editor.RichText");
  7. dojo.require("dijit._Widget");
  8. dojo.require("dijit._editor.selection");
  9. dojo.require("dojo.i18n");
  10. dojo.requireLocalization("dijit", "Textarea", null, "ROOT");
  11. // used to restore content when user leaves this page then comes back
  12. // but do not try doing document.write if we are using xd loading.
  13. // document.write will only work if RichText.js is included in the dojo.js
  14. // file. If it is included in dojo.js and you want to allow rich text saving
  15. // for back/forward actions, then set djConfig.allowXdRichTextSave = true.
  16. if (!djConfig["useXDomain"] || djConfig["allowXdRichTextSave"]) {
  17. if (dojo._postLoad) {
  18. (function() {
  19. var savetextarea = dojo.doc.createElement('textarea');
  20. savetextarea.id = "dijit._editor.RichText.savedContent";
  21. var s = savetextarea.style;
  22. s.display = 'none';
  23. s.position = 'absolute';
  24. s.top = "-100px";
  25. s.left = "-100px"
  26. s.height = "3px";
  27. s.width = "3px";
  28. dojo.body().appendChild(savetextarea);
  29. })();
  30. } else {
  31. // dojo.body() is not available before onLoad is fired
  32. try {
  33. dojo.doc
  34. .write('<textarea id="dijit._editor.RichText.savedContent" '
  35. + 'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>');
  36. } catch (e) {
  37. }
  38. }
  39. }
  40. dojo.declare("dijit._editor.RichText", [dijit._Widget], {
  41. constructor : function() {
  42. // summary:
  43. // dijit._editor.RichText is the core of the WYSIWYG editor in dojo,
  44. // which
  45. // provides the basic editing features. It also encapsulates the
  46. // differences
  47. // of different js engines for various browsers
  48. //
  49. // contentPreFilters: Array
  50. // pre content filter function register array.
  51. // these filters will be executed before the actual
  52. // editing area get the html content
  53. this.contentPreFilters = [];
  54. // contentPostFilters: Array
  55. // post content filter function register array.
  56. // these will be used on the resulting html
  57. // from contentDomPostFilters. The resuling
  58. // content is the final html (returned by getValue())
  59. this.contentPostFilters = [];
  60. // contentDomPreFilters: Array
  61. // pre content dom filter function register array.
  62. // these filters are applied after the result from
  63. // contentPreFilters are set to the editing area
  64. this.contentDomPreFilters = [];
  65. // contentDomPostFilters: Array
  66. // post content dom filter function register array.
  67. // these filters are executed on the editing area dom
  68. // the result from these will be passed to contentPostFilters
  69. this.contentDomPostFilters = [];
  70. // editingAreaStyleSheets: Array
  71. // array to store all the stylesheets applied to the editing area
  72. this.editingAreaStyleSheets = [];
  73. this._keyHandlers = {};
  74. this.contentPreFilters.push(dojo
  75. .hitch(this, "_preFixUrlAttributes"));
  76. if (dojo.isMoz) {
  77. this.contentPreFilters.push(this._fixContentForMoz);
  78. }
  79. // this.contentDomPostFilters.push(this._postDomFixUrlAttributes);
  80. this.onLoadDeferred = new dojo.Deferred();
  81. },
  82. // inheritWidth: Boolean
  83. // whether to inherit the parent's width or simply use 100%
  84. inheritWidth : false,
  85. // focusOnLoad: Boolean
  86. // whether focusing into this instance of richtext when page onload
  87. focusOnLoad : false,
  88. // name: String
  89. // If a save name is specified the content is saved and restored when
  90. // the user
  91. // leave this page can come back, or if the editor is not properly
  92. // closed after
  93. // editing has started.
  94. name : "",
  95. // styleSheets: String
  96. // semicolon (";") separated list of css files for the editing area
  97. styleSheets : "",
  98. // _content: String
  99. // temporary content storage
  100. _content : "",
  101. // height: String
  102. // set height to fix the editor at a specific height, with scrolling.
  103. // By default, this is 300px. If you want to have the editor always
  104. // resizes to accommodate the content, use AlwaysShowToolbar plugin
  105. // and set height=""
  106. height : "300px",
  107. // minHeight: String
  108. // The minimum height that the editor should have
  109. minHeight : "1em",
  110. // isClosed: Boolean
  111. isClosed : true,
  112. // isLoaded: Boolean
  113. isLoaded : false,
  114. // _SEPARATOR: String
  115. // used to concat contents from multiple textareas into a single string
  116. _SEPARATOR : "@@**%%__RICHTEXTBOUNDRY__%%**@@",
  117. // onLoadDeferred: dojo.Deferred
  118. // deferred which is fired when the editor finishes loading
  119. onLoadDeferred : null,
  120. postCreate : function() {
  121. // summary: init
  122. dojo.publish("dijit._editor.RichText::init", [this]);
  123. this.open();
  124. this.setupDefaultShortcuts();
  125. },
  126. setupDefaultShortcuts : function() {
  127. // summary: add some default key handlers
  128. // description:
  129. // Overwrite this to setup your own handlers. The default
  130. // implementation does not use Editor commands, but directly
  131. // executes the builtin commands within the underlying browser
  132. // support.
  133. var ctrl = this.KEY_CTRL;
  134. var exec = function(cmd, arg) {
  135. return arguments.length == 1 ? function() {
  136. this.execCommand(cmd);
  137. } : function() {
  138. this.execCommand(cmd, arg);
  139. }
  140. }
  141. this.addKeyHandler("b", ctrl, exec("bold"));
  142. this.addKeyHandler("i", ctrl, exec("italic"));
  143. this.addKeyHandler("u", ctrl, exec("underline"));
  144. this.addKeyHandler("a", ctrl, exec("selectall"));
  145. this.addKeyHandler("s", ctrl, function() {
  146. this.save(true);
  147. });
  148. this.addKeyHandler("1", ctrl, exec("formatblock", "h1"));
  149. this.addKeyHandler("2", ctrl, exec("formatblock", "h2"));
  150. this.addKeyHandler("3", ctrl, exec("formatblock", "h3"));
  151. this.addKeyHandler("4", ctrl, exec("formatblock", "h4"));
  152. this.addKeyHandler("\\", ctrl, exec("insertunorderedlist"));
  153. if (!dojo.isIE) {
  154. this.addKeyHandler("Z", ctrl, exec("redo"));
  155. }
  156. },
  157. // events: Array
  158. // events which should be connected to the underlying editing area
  159. events : ["onKeyPress", "onKeyDown", "onKeyUp", "onClick"],
  160. // events: Array
  161. // events which should be connected to the underlying editing
  162. // area, events in this array will be addListener with
  163. // capture=true
  164. captureEvents : [],
  165. _editorCommandsLocalized : false,
  166. _localizeEditorCommands : function() {
  167. if (this._editorCommandsLocalized) {
  168. return;
  169. }
  170. this._editorCommandsLocalized = true;
  171. // in IE, names for blockformat is locale dependent, so we cache the
  172. // values here
  173. // if the normal way fails, we try the hard way to get the list
  174. // do not use _cacheLocalBlockFormatNames here, as it will
  175. // trigger security warning in IE7
  176. // in the array below, ul can not come directly after ol,
  177. // otherwise the queryCommandValue returns Normal for it
  178. var formats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5',
  179. 'h6', 'ol', 'div', 'ul'];
  180. var localhtml = "", format, i = 0;
  181. while ((format = formats[i++])) {
  182. if (format.charAt(1) != 'l') {
  183. localhtml += "<" + format + "><span>content</span></"
  184. + format + ">";
  185. } else {
  186. localhtml += "<" + format + "><li>content</li></" + format
  187. + ">";
  188. }
  189. }
  190. // queryCommandValue returns empty if we hide editNode, so move it
  191. // out of screen temporary
  192. var div = document.createElement('div');
  193. div.style.position = "absolute";
  194. div.style.left = "-2000px";
  195. div.style.top = "-2000px";
  196. document.body.appendChild(div);
  197. div.innerHTML = localhtml;
  198. var node = div.firstChild;
  199. while (node) {
  200. dijit._editor.selection.selectElement(node.firstChild);
  201. dojo.withGlobal(this.window, "selectElement",
  202. dijit._editor.selection, [node.firstChild]);
  203. var nativename = node.tagName.toLowerCase();
  204. this._local2NativeFormatNames[nativename] = document
  205. .queryCommandValue("formatblock");// this.queryCommandValue("formatblock");
  206. this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
  207. node = node.nextSibling;
  208. }
  209. document.body.removeChild(div);
  210. },
  211. open : function(/* DomNode? */element) {
  212. // summary:
  213. // Transforms the node referenced in this.domNode into a rich text
  214. // editing
  215. // node. This will result in the creation and replacement with an
  216. // <iframe>
  217. // if designMode(FF)/contentEditable(IE) is used.
  218. if ((!this.onLoadDeferred) || (this.onLoadDeferred.fired >= 0)) {
  219. this.onLoadDeferred = new dojo.Deferred();
  220. }
  221. if (!this.isClosed) {
  222. this.close();
  223. }
  224. dojo.publish("dijit._editor.RichText::open", [this]);
  225. this._content = "";
  226. if ((arguments.length == 1) && (element["nodeName"])) {
  227. this.domNode = element;
  228. } // else unchanged
  229. if ((this.domNode["nodeName"])
  230. && (this.domNode.nodeName.toLowerCase() == "textarea")) {
  231. // if we were created from a textarea, then we need to create a
  232. // new editing harness node.
  233. this.textarea = this.domNode;
  234. this.name = this.textarea.name;
  235. var html = this._preFilterContent(this.textarea.value);
  236. this.domNode = dojo.doc.createElement("div");
  237. this.domNode.setAttribute('widgetId', this.id);
  238. this.textarea.removeAttribute('widgetId');
  239. this.domNode.cssText = this.textarea.cssText;
  240. this.domNode.className += " " + this.textarea.className;
  241. dojo.place(this.domNode, this.textarea, "before");
  242. var tmpFunc = dojo.hitch(this, function() {
  243. // some browsers refuse to submit display=none
  244. // textarea, so
  245. // move the textarea out of screen instead
  246. with (this.textarea.style) {
  247. display = "block";
  248. position = "absolute";
  249. left = top = "-1000px";
  250. if (dojo.isIE) { // nasty IE bug: abnormal
  251. // formatting if overflow is
  252. // not hidden
  253. this.__overflow = overflow;
  254. overflow = "hidden";
  255. }
  256. }
  257. });
  258. if (dojo.isIE) {
  259. setTimeout(tmpFunc, 10);
  260. } else {
  261. tmpFunc();
  262. }
  263. // this.domNode.innerHTML = html;
  264. // if(this.textarea.form){
  265. // // FIXME: port: this used to be before advice!!!
  266. // dojo.connect(this.textarea.form, "onsubmit", this,
  267. // function(){
  268. // // FIXME: should we be calling close() here instead?
  269. // this.textarea.value = this.getValue();
  270. // });
  271. // }
  272. } else {
  273. var html = this._preFilterContent(this
  274. .getNodeChildrenHtml(this.domNode));
  275. this.domNode.innerHTML = '';
  276. }
  277. if (html == "") {
  278. html = "&nbsp;";
  279. }
  280. var content = dojo.contentBox(this.domNode);
  281. // var content = dojo.contentBox(this.srcNodeRef);
  282. this._oldHeight = content.h;
  283. this._oldWidth = content.w;
  284. this.savedContent = html;
  285. // If we're a list item we have to put in a blank line to force the
  286. // bullet to nicely align at the top of text
  287. if ((this.domNode["nodeName"]) && (this.domNode.nodeName == "LI")) {
  288. this.domNode.innerHTML = " <br>";
  289. }
  290. this.editingArea = dojo.doc.createElement("div");
  291. this.domNode.appendChild(this.editingArea);
  292. if (this.name != ""
  293. && (!djConfig["useXDomain"] || djConfig["allowXdRichTextSave"])) {
  294. var saveTextarea = dojo
  295. .byId("dijit._editor.RichText.savedContent");
  296. if (saveTextarea.value != "") {
  297. var datas = saveTextarea.value.split(this._SEPARATOR), i = 0, dat;
  298. while ((dat = datas[i++])) {
  299. var data = dat.split(":");
  300. if (data[0] == this.name) {
  301. html = data[1];
  302. datas.splice(i, 1);
  303. break;
  304. }
  305. }
  306. }
  307. // FIXME: need to do something different for Opera/Safari
  308. dojo.connect(window, "onbeforeunload", this, "_saveContent");
  309. // dojo.connect(window, "onunload", this, "_saveContent");
  310. }
  311. this.isClosed = false;
  312. // Safari's selections go all out of whack if we do it inline,
  313. // so for now IE is our only hero
  314. // if (typeof document.body.contentEditable != "undefined") {
  315. if (dojo.isIE || dojo.isSafari || dojo.isOpera) { // contentEditable,
  316. // easy
  317. var ifr = this.iframe = dojo.doc.createElement('iframe');
  318. ifr.src = 'javascript:void(0)';
  319. this.editorObject = ifr;
  320. ifr.style.border = "none";
  321. ifr.style.width = "100%";
  322. ifr.frameBorder = 0;
  323. // ifr.style.scrolling = this.height ? "auto" : "vertical";
  324. this.editingArea.appendChild(ifr);
  325. this.window = ifr.contentWindow;
  326. this.document = this.window.document;
  327. this.document.open();
  328. this.document.write(this._getIframeDocTxt(html));
  329. this.document.close();
  330. if (dojo.isIE >= 7) {
  331. if (this.height) {
  332. ifr.style.height = this.height;
  333. }
  334. if (this.minHeight) {
  335. ifr.style.minHeight = this.minHeight;
  336. }
  337. } else {
  338. ifr.style.height = this.height
  339. ? this.height
  340. : this.minHeight;
  341. }
  342. if (dojo.isIE) {
  343. this._localizeEditorCommands();
  344. }
  345. this.onLoad();
  346. } else { // designMode in iframe
  347. this._drawIframe(html);
  348. }
  349. // TODO: this is a guess at the default line-height, kinda works
  350. if (this.domNode.nodeName == "LI") {
  351. this.domNode.lastChild.style.marginTop = "-1.2em";
  352. }
  353. this.domNode.className += " RichTextEditable";
  354. },
  355. // static cache variables shared among all instance of this class
  356. _local2NativeFormatNames : {},
  357. _native2LocalFormatNames : {},
  358. _localizedIframeTitles : null,
  359. _getIframeDocTxt : function(/* String */html) {
  360. var _cs = dojo.getComputedStyle(this.domNode);
  361. if (!this.height && !dojo.isMoz) {
  362. html = "<div>" + html + "</div>";
  363. }
  364. var font = [_cs.fontWeight, _cs.fontSize, _cs.fontFamily].join(" ");
  365. // line height is tricky - applying a units value will mess things
  366. // up.
  367. // if we can't get a non-units value, bail out.
  368. var lineHeight = _cs.lineHeight;
  369. if (lineHeight.indexOf("px") >= 0) {
  370. lineHeight = parseFloat(lineHeight) / parseFloat(_cs.fontSize);
  371. // console.debug(lineHeight);
  372. } else if (lineHeight.indexOf("em") >= 0) {
  373. lineHeight = parseFloat(lineHeight);
  374. } else {
  375. lineHeight = "1.0";
  376. }
  377. return [
  378. this.isLeftToRight()
  379. ? "<html><head>"
  380. : "<html dir='rtl'><head>",
  381. (dojo.isMoz ? "<title>"
  382. + this._localizedIframeTitles.iframeEditTitle
  383. + "</title>" : ""),
  384. "<style>",
  385. "body,html {",
  386. " background:transparent;",
  387. " padding: 0;",
  388. " margin: 0;",
  389. "}",
  390. // TODO: left positioning will cause contents to disappear
  391. // out of view
  392. // if it gets too wide for the visible area
  393. "body{",
  394. " top:0px; left:0px; right:0px;",
  395. ((this.height || dojo.isOpera) ? "" : "position: fixed;"),
  396. " font:",
  397. font,
  398. ";",
  399. // FIXME: IE 6 won't understand min-height?
  400. " min-height:",
  401. this.minHeight,
  402. ";",
  403. " line-height:",
  404. lineHeight,
  405. "}",
  406. "p{ margin: 1em 0 !important; }",
  407. (this.height
  408. ? ""
  409. : "body,html{overflow-y:hidden;/*for IE*/} body > div {overflow-x:auto;/*for FF to show vertical scrollbar*/}"),
  410. "li > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; } ",
  411. "li{ min-height:1.2em; }", "</style>",
  412. this._applyEditingAreaStyleSheets(),
  413. "</head><body>" + html + "</body></html>"].join(""); // String
  414. },
  415. _drawIframe : function(/* String */html) {
  416. // summary:
  417. // Draws an iFrame using the existing one if one exists.
  418. // Used by Mozilla, Safari, and Opera
  419. if (!this.iframe) {
  420. var ifr = this.iframe = dojo.doc.createElement("iframe");
  421. // this.iframe.src = "about:blank";
  422. // document.body.appendChild(this.iframe);
  423. // console.debug(this.iframe.contentDocument.open());
  424. // dojo.body().appendChild(this.iframe);
  425. var ifrs = ifr.style;
  426. // ifrs.border = "1px solid black";
  427. ifrs.border = "none";
  428. ifrs.lineHeight = "0"; // squash line height
  429. ifrs.verticalAlign = "bottom";
  430. // ifrs.scrolling = this.height ? "auto" : "vertical";
  431. this.editorObject = this.iframe;
  432. // get screen reader text for mozilla here, too
  433. this._localizedIframeTitles = dojo.i18n.getLocalization(
  434. "dijit", "Textarea");
  435. // need to find any associated label element and update iframe
  436. // document title
  437. var label = dojo.query('label[for="' + this.id + '"]');
  438. if (label.length) {
  439. this._localizedIframeTitles.iframeEditTitle = label[0].innerHTML
  440. + " " + this._localizedIframeTitles.iframeEditTitle;
  441. }
  442. }
  443. // opera likes this to be outside the with block
  444. // this.iframe.src =
  445. // "javascript:void(0)";//dojo.uri.dojoUri("src/widget/templates/richtextframe.html")
  446. // + ((dojo.doc.domain != currentDomain) ? ("#"+dojo.doc.domain) :
  447. // "");
  448. this.iframe.style.width = this.inheritWidth
  449. ? this._oldWidth
  450. : "100%";
  451. if (this.height) {
  452. this.iframe.style.height = this.height;
  453. } else {
  454. this.iframe.height = this._oldHeight;
  455. }
  456. if (this.textarea) {
  457. var tmpContent = this.srcNodeRef;
  458. } else {
  459. var tmpContent = dojo.doc.createElement('div');
  460. tmpContent.style.display = "none";
  461. tmpContent.innerHTML = html;
  462. // append tmpContent to under the current domNode so that the
  463. // margin
  464. // calculation below is correct
  465. this.editingArea.appendChild(tmpContent);
  466. }
  467. this.editingArea.appendChild(this.iframe);
  468. // do we want to show the content before the editing area finish
  469. // loading here?
  470. // if external style sheets are used for the editing area, the
  471. // appearance now
  472. // and after loading of the editing area won't be the same (and
  473. // padding/margin
  474. // calculation above may not be accurate)
  475. // tmpContent.style.display = "none";
  476. // this.editingArea.appendChild(this.iframe);
  477. var _iframeInitialized = false;
  478. // console.debug(this.iframe);
  479. // var contentDoc = this.iframe.contentWindow.document;
  480. // note that on Safari lower than 420+, we have to get the iframe
  481. // by ID in order to get something w/ a contentDocument property
  482. var contentDoc = this.iframe.contentDocument;
  483. contentDoc.open();
  484. contentDoc.write(this._getIframeDocTxt(html));
  485. contentDoc.close();
  486. // now we wait for onload. Janky hack!
  487. var ifrFunc = dojo.hitch(this, function() {
  488. if (!_iframeInitialized) {
  489. _iframeInitialized = true;
  490. } else {
  491. return;
  492. }
  493. if (!this.editNode) {
  494. try {
  495. if (this.iframe.contentWindow) {
  496. this.window = this.iframe.contentWindow;
  497. this.document = this.iframe.contentWindow.document
  498. } else if (this.iframe.contentDocument) {
  499. // for opera
  500. this.window = this.iframe.contentDocument.window;
  501. this.document = this.iframe.contentDocument;
  502. }
  503. if (!this.document.body) {
  504. throw 'Error';
  505. }
  506. } catch (e) {
  507. setTimeout(ifrFunc, 500);
  508. _iframeInitialized = false;
  509. return;
  510. }
  511. dojo._destroyElement(tmpContent);
  512. this.document.designMode = "on";
  513. // try{
  514. // this.document.designMode = "on";
  515. // }catch(e){
  516. // this._tryDesignModeOnClick=true;
  517. // }
  518. this.onLoad();
  519. } else {
  520. dojo._destroyElement(tmpContent);
  521. this.editNode.innerHTML = html;
  522. this.onDisplayChanged();
  523. }
  524. this._preDomFilterContent(this.editNode);
  525. });
  526. ifrFunc();
  527. },
  528. _applyEditingAreaStyleSheets : function() {
  529. // summary:
  530. // apply the specified css files in styleSheets
  531. var files = [];
  532. if (this.styleSheets) {
  533. files = this.styleSheets.split(';');
  534. this.styleSheets = '';
  535. }
  536. // empty this.editingAreaStyleSheets here, as it will be filled in
  537. // addStyleSheet
  538. files = files.concat(this.editingAreaStyleSheets);
  539. this.editingAreaStyleSheets = [];
  540. var text = '', i = 0, url;
  541. while ((url = files[i++])) {
  542. var abstring = (new dojo._Url(dojo.global.location, url))
  543. .toString();
  544. this.editingAreaStyleSheets.push(abstring);
  545. text += '<link rel="stylesheet" type="text/css" href="'
  546. + abstring + '"/>'
  547. }
  548. return text;
  549. },
  550. addStyleSheet : function(/* dojo._Url */uri) {
  551. // summary:
  552. // add an external stylesheet for the editing area
  553. // uri: a dojo.uri.Uri pointing to the url of the external css file
  554. var url = uri.toString();
  555. // if uri is relative, then convert it to absolute so that it can be
  556. // resolved correctly in iframe
  557. if (url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)) {
  558. url = (new dojo._Url(dojo.global.location, url)).toString();
  559. }
  560. if (dojo.indexOf(this.editingAreaStyleSheets, url) > -1) {
  561. console
  562. .debug("dijit._editor.RichText.addStyleSheet: Style sheet "
  563. + url
  564. + " is already applied to the editing area!");
  565. return;
  566. }
  567. this.editingAreaStyleSheets.push(url);
  568. if (this.document.createStyleSheet) { // IE
  569. this.document.createStyleSheet(url);
  570. } else { // other browser
  571. var head = this.document.getElementsByTagName("head")[0];
  572. var stylesheet = this.document.createElement("link");
  573. with (stylesheet) {
  574. rel = "stylesheet";
  575. type = "text/css";
  576. href = url;
  577. }
  578. head.appendChild(stylesheet);
  579. }
  580. },
  581. removeStyleSheet : function(/* dojo._Url */uri) {
  582. // summary:
  583. // remove an external stylesheet for the editing area
  584. var url = uri.toString();
  585. // if uri is relative, then convert it to absolute so that it can be
  586. // resolved correctly in iframe
  587. if (url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)) {
  588. url = (new dojo._Url(dojo.global.location, url)).toString();
  589. }
  590. var index = dojo.indexOf(this.editingAreaStyleSheets, url);
  591. if (index == -1) {
  592. console
  593. .debug("dijit._editor.RichText.removeStyleSheet: Style sheet "
  594. + url
  595. + " is not applied to the editing area so it can not be removed!");
  596. return;
  597. }
  598. delete this.editingAreaStyleSheets[index];
  599. dojo.withGlobal(this.window, 'query', dojo,
  600. ['link:[href="' + url + '"]']).orphan()
  601. },
  602. disabled : false,
  603. _mozSettingProps : ['styleWithCSS', 'insertBrOnReturn'],
  604. setDisabled : function(/* Boolean */disabled) {
  605. if (dojo.isIE || dojo.isSafari || dojo.isOpera) {
  606. this.editNode.contentEditable = !disabled;
  607. } else { // moz
  608. if (disabled) {
  609. this._mozSettings = [false, this.blockNodeForEnter === 'BR'];
  610. }
  611. this.document.designMode = (disabled ? 'off' : 'on');
  612. if (!disabled) {
  613. dojo.forEach(this._mozSettingProps, function(s, i) {
  614. this.document.execCommand(s, false,
  615. this._mozSettings[i]);
  616. }, this);
  617. }
  618. // this.document.execCommand('contentReadOnly', false,
  619. // disabled);
  620. // if(disabled){
  621. // this.blur(); //to remove the blinking caret
  622. // }
  623. //
  624. }
  625. this.disabled = disabled;
  626. },
  627. /***********************************************************************
  628. * Event handlers
  629. **********************************************************************/
  630. _isResized : function() {
  631. return false;
  632. },
  633. onLoad : function(/* Event */e) {
  634. // summary: handler after the content of the document finishes
  635. // loading
  636. this.isLoaded = true;
  637. if (this.height || dojo.isMoz) {
  638. this.editNode = this.document.body;
  639. } else {
  640. this.editNode = this.document.body.firstChild;
  641. }
  642. this.editNode.contentEditable = true; // should do no harm in FF
  643. this._preDomFilterContent(this.editNode);
  644. var events = this.events.concat(this.captureEvents), i = 0, et;
  645. while ((et = events[i++])) {
  646. this.connect(this.document, et.toLowerCase(), et);
  647. }
  648. if (!dojo.isIE) {
  649. try { // sanity check for Mozilla
  650. // this.document.execCommand("useCSS", false, true); // old moz
  651. // call
  652. this.document.execCommand("styleWithCSS", false, false); // new
  653. // moz
  654. // call
  655. // this.document.execCommand("insertBrOnReturn", false,
  656. // false); // new moz call
  657. } catch (e2) {
  658. }
  659. // FIXME: when scrollbars appear/disappear this needs to be
  660. // fired
  661. } else { // IE contentEditable
  662. // give the node Layout on IE
  663. this.editNode.style.zoom = 1.0;
  664. }
  665. if (this.focusOnLoad) {
  666. this.focus();
  667. }
  668. this.onDisplayChanged(e);
  669. if (this.onLoadDeferred) {
  670. this.onLoadDeferred.callback(true);
  671. }
  672. },
  673. onKeyDown : function(/* Event */e) {
  674. // summary: Fired on keydown
  675. // console.info("onkeydown:", e.keyCode);
  676. // we need this event at the moment to get the events from control
  677. // keys
  678. // such as the backspace. It might be possible to add this to Dojo,
  679. // so that
  680. // keyPress events can be emulated by the keyDown and keyUp
  681. // detection.
  682. if (dojo.isIE) {
  683. if (e.keyCode === dojo.keys.BACKSPACE
  684. && this.document.selection.type === "Control") {
  685. // IE has a bug where if a non-text object is selected in
  686. // the editor,
  687. // hitting backspace would act as if the browser's back
  688. // button was
  689. // clicked instead of deleting the object. see #1069
  690. dojo.stopEvent(e);
  691. this.execCommand("delete");
  692. } else if ((65 <= e.keyCode && e.keyCode <= 90)
  693. || (e.keyCode >= 37 && e.keyCode <= 40) // FIXME: get
  694. // this from
  695. // connect()
  696. // instead!
  697. ) { // arrow keys
  698. e.charCode = e.keyCode;
  699. this.onKeyPress(e);
  700. }
  701. } else if (dojo.isMoz) {
  702. if (e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey
  703. && !e.altKey && this.iframe) {
  704. // update iframe document title for screen reader
  705. this.iframe.contentDocument.title = this._localizedIframeTitles.iframeFocusTitle;
  706. // Place focus on the iframe. A subsequent tab or shift tab
  707. // will put focus
  708. // on the correct control.
  709. this.iframe.focus(); // this.focus(); won't work
  710. dojo.stopEvent(e);
  711. } else if (e.keyCode == dojo.keys.TAB && e.shiftKey) {
  712. // if there is a toolbar, set focus to it, otherwise ignore
  713. if (this.toolbar) {
  714. this.toolbar.focus();
  715. }
  716. dojo.stopEvent(e);
  717. }
  718. }
  719. },
  720. onKeyUp : function(e) {
  721. // summary: Fired on keyup
  722. return;
  723. },
  724. KEY_CTRL : 1,
  725. KEY_SHIFT : 2,
  726. onKeyPress : function(e) {
  727. // summary: Fired on keypress
  728. // console.info("onkeypress:", e.keyCode);
  729. // handle the various key events
  730. var modifiers = e.ctrlKey ? this.KEY_CTRL : 0 | e.shiftKey
  731. ? this.KEY_SHIFT
  732. : 0;
  733. var key = e.keyChar || e.keyCode;
  734. if (this._keyHandlers[key]) {
  735. // console.debug("char:", e.key);
  736. var handlers = this._keyHandlers[key], i = 0, h;
  737. while ((h = handlers[i++])) {
  738. if (modifiers == h.modifiers) {
  739. if (!h.handler.apply(this, arguments)) {
  740. e.preventDefault();
  741. }
  742. break;
  743. }
  744. }
  745. }
  746. // function call after the character has been inserted
  747. setTimeout(dojo.hitch(this, function() {
  748. this.onKeyPressed(e);
  749. }), 1);
  750. },
  751. addKeyHandler : function(/* String */key, /* Int */modifiers, /* Function */
  752. handler) {
  753. // summary: add a handler for a keyboard shortcut
  754. if (!dojo.isArray(this._keyHandlers[key])) {
  755. this._keyHandlers[key] = [];
  756. }
  757. this._keyHandlers[key].push({
  758. modifiers : modifiers || 0,
  759. handler : handler
  760. });
  761. },
  762. onKeyPressed : function(/* Event */e) {
  763. this.onDisplayChanged(/* e */); // can't pass in e
  764. },
  765. onClick : function(/* Event */e) {
  766. // console.debug('onClick',this._tryDesignModeOnClick);
  767. // if(this._tryDesignModeOnClick){
  768. // try{
  769. // this.document.designMode='on';
  770. // this._tryDesignModeOnClick=false;
  771. // }catch(e){}
  772. // }
  773. this.onDisplayChanged(e);
  774. },
  775. _onBlur : function(e) {
  776. var _c = this.getValue(true);
  777. if (_c != this.savedContent) {
  778. this.onChange(_c);
  779. this.savedContent = _c;
  780. }
  781. if (dojo.isMoz && this.iframe) {
  782. this.iframe.contentDocument.title = this._localizedIframeTitles.iframeEditTitle;
  783. }
  784. // console.info('_onBlur')
  785. },
  786. _initialFocus : true,
  787. _onFocus : function(/* Event */e) {
  788. // console.info('_onFocus')
  789. // summary: Fired on focus
  790. if ((dojo.isMoz) && (this._initialFocus)) {
  791. this._initialFocus = false;
  792. if (this.editNode.innerHTML.replace(/^\s+|\s+$/g, "") == "&nbsp;") {
  793. this.placeCursorAtStart();
  794. // this.execCommand("selectall");
  795. // this.window.getSelection().collapseToStart();
  796. }
  797. }
  798. },
  799. blur : function() {
  800. // summary: remove focus from this instance
  801. if (this.iframe) {
  802. this.window.blur();
  803. } else if (this.editNode) {
  804. this.editNode.blur();
  805. }
  806. },
  807. focus : function() {
  808. // summary: move focus to this instance
  809. if (this.iframe && !dojo.isIE) {
  810. dijit.focus(this.iframe);
  811. } else if (this.editNode && this.editNode.focus) {
  812. // editNode may be hidden in display:none div, lets just punt in
  813. // this case
  814. dijit.focus(this.editNode);
  815. } else {
  816. console.debug("Have no idea how to focus into the editor!");
  817. }
  818. },
  819. // _lastUpdate: 0,
  820. updateInterval : 200,
  821. _updateTimer : null,
  822. onDisplayChanged : function(/* Event */e) {
  823. // summary:
  824. // This event will be fired everytime the display context
  825. // changes and the result needs to be reflected in the UI.
  826. // description:
  827. // If you don't want to have update too often,
  828. // onNormalizedDisplayChanged should be used instead
  829. // var _t=allGetServerTime();
  830. if (!this._updateTimer) {
  831. // this._lastUpdate=_t;
  832. if (this._updateTimer) {
  833. clearTimeout(this._updateTimer);
  834. }
  835. this._updateTimer = setTimeout(dojo.hitch(this,
  836. this.onNormalizedDisplayChanged),
  837. this.updateInterval);
  838. }
  839. },
  840. onNormalizedDisplayChanged : function() {
  841. // summary:
  842. // This event is fired every updateInterval ms or more
  843. // description:
  844. // If something needs to happen immidiately after a
  845. // user change, please use onDisplayChanged instead
  846. this._updateTimer = null;
  847. },
  848. onChange : function(newContent) {
  849. // summary:
  850. // this is fired if and only if the editor loses focus and
  851. // the content is changed
  852. // console.log('onChange',newContent);
  853. },
  854. _normalizeCommand : function(/* String */cmd) {
  855. // summary:
  856. // Used as the advice function by dojo.connect to map our
  857. // normalized set of commands to those supported by the target
  858. // browser
  859. var command = cmd.toLowerCase();
  860. if (command == "formatblock") {
  861. if (dojo.isSafari) {
  862. command = "heading";
  863. }
  864. } else if (command == "hilitecolor" && !dojo.isMoz) {
  865. command = "backcolor";
  866. }
  867. return command;
  868. },
  869. queryCommandAvailable : function(/* String */command) {
  870. // summary:
  871. // Tests whether a command is supported by the host. Clients SHOULD
  872. // check
  873. // whether a command is supported before attempting to use it,
  874. // behaviour
  875. // for unsupported commands is undefined.
  876. // command: The command to test for
  877. var ie = 1;
  878. var mozilla = 1 << 1;
  879. var safari = 1 << 2;
  880. var opera = 1 << 3;
  881. var safari420 = 1 << 4;
  882. var gt420 = dojo.isSafari;
  883. function isSupportedBy(browsers) {
  884. return {
  885. ie : Boolean(browsers & ie),
  886. mozilla : Boolean(browsers & mozilla),
  887. safari : Boolean(browsers & safari),
  888. safari420 : Boolean(browsers & safari420),
  889. opera : Boolean(browsers & opera)
  890. }
  891. }
  892. var supportedBy = null;
  893. switch (command.toLowerCase()) {
  894. case "bold" :
  895. case "italic" :
  896. case "underline" :
  897. case "subscript" :
  898. case "superscript" :
  899. case "fontname" :
  900. case "fontsize" :
  901. case "forecolor" :
  902. case "hilitecolor" :
  903. case "justifycenter" :
  904. case "justifyfull" :
  905. case "justifyleft" :
  906. case "justifyright" :
  907. case "delete" :
  908. case "selectall" :
  909. supportedBy = isSupportedBy(mozilla | ie | safari | opera);
  910. break;
  911. case "createlink" :
  912. case "unlink" :
  913. case "removeformat" :
  914. case "inserthorizontalrule" :
  915. case "insertimage" :
  916. case "insertorderedlist" :
  917. case "insertunorderedlist" :
  918. case "indent" :
  919. case "outdent" :
  920. case "formatblock" :
  921. case "inserthtml" :
  922. case "undo" :
  923. case "redo" :
  924. case "strikethrough" :
  925. supportedBy = isSupportedBy(mozilla | ie | opera
  926. | safari420);
  927. break;
  928. case "blockdirltr" :
  929. case "blockdirrtl" :
  930. case "dirltr" :
  931. case "dirrtl" :
  932. case "inlinedirltr" :
  933. case "inlinedirrtl" :
  934. supportedBy = isSupportedBy(ie);
  935. break;
  936. case "cut" :
  937. case "copy" :
  938. case "paste" :
  939. supportedBy = isSupportedBy(ie | mozilla | safari420);
  940. break;
  941. case "inserttable" :
  942. supportedBy = isSupportedBy(mozilla | ie);
  943. break;
  944. case "insertcell" :
  945. case "insertcol" :
  946. case "insertrow" :
  947. case "deletecells" :
  948. case "deletecols" :
  949. case "deleterows" :
  950. case "mergecells" :
  951. case "splitcell" :
  952. supportedBy = isSupportedBy(ie | mozilla);
  953. break;
  954. default :
  955. return false;
  956. }
  957. return (dojo.isIE && supportedBy.ie)
  958. || (dojo.isMoz && supportedBy.mozilla)
  959. || (dojo.isSafari && supportedBy.safari)
  960. || (gt420 && supportedBy.safari420)
  961. || (dojo.isOpera && supportedBy.opera); // Boolean return
  962. // true if the
  963. // command is
  964. // supported, false
  965. // otherwise
  966. },
  967. execCommand : function(/* String */command, argument) {
  968. // summary: Executes a command in the Rich Text area
  969. // command: The command to execute
  970. // argument: An optional argument to the command
  971. var returnValue;
  972. // focus() is required for IE to work
  973. // In addition, focus() makes sure after the execution of
  974. // the command, the editor receives the focus as expected
  975. this.focus();
  976. command = this._normalizeCommand(command);
  977. if (argument != undefined) {
  978. if (command == "heading") {
  979. throw new Error("unimplemented");
  980. } else if ((command == "formatblock") && dojo.isIE) {
  981. argument = '<' + argument + '>';
  982. }
  983. }
  984. if (command == "inserthtml") {
  985. // TODO: we shall probably call _preDomFilterContent here as
  986. // well
  987. argument = this._preFilterContent(argument);
  988. if (dojo.isIE) {
  989. var insertRange = this.document.selection.createRange();
  990. insertRange.pasteHTML(argument);
  991. insertRange.select();
  992. // insertRange.collapse(true);
  993. returnValue = true;
  994. } else if (dojo.isMoz && !argument.length) {
  995. // mozilla can not inserthtml an empty html to delete
  996. // current selection
  997. // so we delete the selection instead in this case
  998. dojo.withGlobal(this.window, 'remove',
  999. dijit._editor.selection); // FIXME
  1000. returnValue = true;
  1001. } else {
  1002. returnValue = this.document.execCommand(command, false,
  1003. argument);
  1004. }
  1005. } else if ((command == "unlink")
  1006. && (this.queryCommandEnabled("unlink"))
  1007. && (dojo.isMoz || dojo.isSafari)) {
  1008. // fix up unlink in Mozilla to unlink the link and not just the
  1009. // selection
  1010. // grab selection
  1011. // Mozilla gets upset if we just store the range so we have to
  1012. // get the basic properties and recreate to save the selection
  1013. var selection = this.window.getSelection();
  1014. // var selectionRange = selection.getRangeAt(0);
  1015. // var selectionStartContainer = selectionRange.startContainer;
  1016. // var selectionStartOffset = selectionRange.startOffset;
  1017. // var selectionEndContainer = selectionRange.endContainer;
  1018. // var selectionEndOffset = selectionRange.endOffset;
  1019. // select our link and unlink
  1020. var a = dojo.withGlobal(this.window, "getAncestorElement",
  1021. dijit._editor.selection, ['a']);
  1022. dojo.withGlobal(this.window, "selectElement",
  1023. dijit._editor.selection, [a]);
  1024. returnValue = this.document.execCommand("unlink", false, null);
  1025. } else if ((command == "hilitecolor") && (dojo.isMoz)) {
  1026. // // mozilla doesn't support hilitecolor properly when useCSS
  1027. // is
  1028. // // set to false (bugzilla #279330)
  1029. this.document.execCommand("styleWithCSS", false, true);
  1030. returnValue = this.document.execCommand(command, false,
  1031. argument);
  1032. this.document.execCommand("styleWithCSS", false, false);
  1033. } else if ((dojo.isIE)
  1034. && ((command == "backcolor") || (command == "forecolor"))) {
  1035. // Tested under IE 6 XP2, no problem here, comment out
  1036. // IE weirdly collapses ranges when we exec these commands, so
  1037. // prevent it
  1038. // var tr = this.document.selection.createRange();
  1039. argument = arguments.length > 1 ? argument : null;
  1040. returnValue = this.document.execCommand(command, false,
  1041. argument);
  1042. // timeout is workaround for weird IE behavior were the text
  1043. // selection gets correctly re-created, but subsequent input
  1044. // apparently isn't bound to it
  1045. // setTimeout(function(){tr.select();}, 1);
  1046. } else {
  1047. argument = arguments.length > 1 ? argument : null;
  1048. // if(dojo.isMoz){
  1049. // this.document = this.iframe.contentWindow.document
  1050. // }
  1051. if (argument || command != "createlink") {
  1052. returnValue = this.document.execCommand(command, false,
  1053. argument);
  1054. }
  1055. }
  1056. this.onDisplayChanged();
  1057. return returnValue;
  1058. },
  1059. queryCommandEnabled : function(/* String */command) {
  1060. // summary: check whether a command is enabled or not
  1061. command = this._normalizeCommand(command);
  1062. if (dojo.isMoz || dojo.isSafari) {
  1063. if (command == "unlink") { // mozilla returns true always
  1064. // console.debug(dojo.withGlobal(this.window,
  1065. // "hasAncestorElement",dijit._editor.selection, ['a']));
  1066. return dojo.withGlobal(this.window, "hasAncestorElement",
  1067. dijit._editor.selection, ['a']);
  1068. } else if (command == "inserttable") {
  1069. return true;
  1070. }
  1071. }
  1072. // see #4109
  1073. if (dojo.isSafari)
  1074. if (command == "copy") {
  1075. command = "cut";
  1076. } else if (command == "paste") {
  1077. return true;
  1078. }
  1079. // return this.document.queryCommandEnabled(command);
  1080. var elem = (dojo.isIE)
  1081. ? this.document.selection.createRange()
  1082. : this.document;
  1083. return elem.queryCommandEnabled(command);
  1084. },
  1085. queryCommandState : function(command) {
  1086. // summary: check the state of a given command
  1087. command = this._normalizeCommand(command);
  1088. return this.document.queryCommandState(command);
  1089. },
  1090. queryCommandValue : function(command) {
  1091. // summary: check the value of a given command
  1092. command = this._normalizeCommand(command);
  1093. if (dojo.isIE && command == "formatblock") {
  1094. return this._local2NativeFormatNames[this.document
  1095. .queryCommandValue(command)];
  1096. }
  1097. return this.document.queryCommandValue(command);
  1098. },
  1099. // Misc.
  1100. placeCursorAtStart : function() {
  1101. // summary:
  1102. // place the cursor at the start of the editing area
  1103. this.focus();
  1104. // see comments in placeCursorAtEnd
  1105. var isvalid = false;
  1106. if (dojo.isMoz) {
  1107. var first = this.editNode.firstChild;
  1108. while (first) {
  1109. if (first.nodeType == 3) {
  1110. if (first.nodeValue.replace(/^\s+|\s+$/g, "").length > 0) {
  1111. isvalid = true;
  1112. dojo.withGlobal(this.window, "selectElement",
  1113. dijit._editor.selection, [first]);
  1114. break;
  1115. }
  1116. } else if (first.nodeType == 1) {
  1117. isvalid = true;
  1118. dojo.withGlobal(this.window, "selectElementChildren",
  1119. dijit._editor.selection, [first]);
  1120. break;
  1121. }
  1122. first = first.nextSibling;
  1123. }
  1124. } else {
  1125. isvalid = true;
  1126. dojo.withGlobal(this.window, "selectElementChildren",
  1127. dijit._editor.selection, [this.editNode]);
  1128. }
  1129. if (isvalid) {
  1130. dojo.withGlobal(this.window, "collapse",
  1131. dijit._editor.selection, [true]);
  1132. }
  1133. },
  1134. placeCursorAtEnd : function() {
  1135. // summary:
  1136. // place the cursor at the end of the editing area
  1137. this.focus();
  1138. // In mozilla, if last child is not a text node, we have to use
  1139. // selectElementChildren on this.editNode.lastChild
  1140. // otherwise the cursor would be placed at the end of the closing
  1141. // tag of this.editNode.lastChild
  1142. var isvalid = false;
  1143. if (dojo.isMoz) {
  1144. var last = this.editNode.lastChild;
  1145. while (last) {
  1146. if (last.nodeType == 3) {
  1147. if (last.nodeValue.replace(/^\s+|\s+$/g, "").length > 0) {
  1148. isvalid = true;
  1149. dojo.withGlobal(this.window, "selectElement",
  1150. dijit._editor.selection, [last]);
  1151. break;
  1152. }
  1153. } else if (last.nodeType == 1) {
  1154. isvalid = true;
  1155. if (last.lastChild) {
  1156. dojo.withGlobal(this.window, "selectElement",
  1157. dijit._editor.selection, [last.lastChild]);
  1158. } else {
  1159. dojo.withGlobal(this.window, "selectElement",
  1160. dijit._editor.selection, [last]);
  1161. }
  1162. break;
  1163. }
  1164. last = last.previousSibling;
  1165. }
  1166. } else {
  1167. isvalid = true;
  1168. dojo.withGlobal(this.window, "selectElementChildren",
  1169. dijit._editor.selection, [this.editNode]);
  1170. }
  1171. if (isvalid) {
  1172. dojo.withGlobal(this.window, "collapse",
  1173. dijit._editor.selection, [false]);
  1174. }
  1175. },
  1176. getValue : function(/* Boolean? */nonDestructive) {
  1177. // summary:
  1178. // return the current content of the editing area (post filters are
  1179. // applied)
  1180. if (this.textarea) {
  1181. if (this.isClosed || !this.isLoaded) {
  1182. return this.textarea.value;
  1183. }
  1184. }
  1185. return this._postFilterContent(null, nonDestructive);
  1186. },
  1187. setValue : function(/* String */html) {
  1188. // summary:
  1189. // this function set the content. No undo history is preserved
  1190. if (this.textarea && (this.isClosed || !this.isLoaded)) {
  1191. this.textarea.value = html;
  1192. } else {
  1193. html = this._preFilterContent(html);
  1194. if (this.isClosed) {
  1195. this.domNode.innerHTML = html;
  1196. this._preDomFilterContent(this.domNode);
  1197. } else {
  1198. this.editNode.innerHTML = html;
  1199. this._preDomFilterContent(this.editNode);
  1200. }
  1201. }
  1202. },
  1203. replaceValue : function(/* String */html) {
  1204. // summary:
  1205. // this function set the content while trying to maintain the undo
  1206. // stack
  1207. // (now only works fine with Moz, this is identical to setValue in
  1208. // all
  1209. // other browsers)
  1210. if (this.isClosed) {
  1211. this.setValue(html);
  1212. } else if (this.window && this.window.getSelection && !dojo.isMoz) { // Safari
  1213. // look ma! it's a totally f'd browser!
  1214. this.setValue(html);
  1215. } else if (this.window && this.window.getSelection) { // Moz
  1216. html = this._preFilterContent(html);
  1217. this.execCommand("selectall");
  1218. if (dojo.isMoz && !html) {
  1219. html = "&nbsp;"
  1220. }
  1221. this.execCommand("inserthtml", html);
  1222. this._preDomFilterContent(this.editNode);
  1223. } else if (this.document && this.document.selection) {// IE
  1224. // In IE, when the first element is not a text node, say
  1225. // an <a> tag, when replacing the content of the editing
  1226. // area, the <a> tag will be around all the content
  1227. // so for now, use setValue for IE too
  1228. this.setValue(html);
  1229. }
  1230. },
  1231. _preFilterContent : function(/* String */html) {
  1232. // summary:
  1233. // filter the input before setting the content of the editing area
  1234. var ec = html;
  1235. dojo.forEach(this.contentPreFilters, function(ef) {
  1236. if (ef) {
  1237. ec = ef(ec);
  1238. }
  1239. });
  1240. return ec;
  1241. },
  1242. _preDomFilterContent : function(/* DomNode */dom) {
  1243. // summary:
  1244. // filter the input
  1245. dom = dom || this.editNode;
  1246. dojo.forEach(this.contentDomPreFilters, function(ef) {
  1247. if (ef && dojo.isFunction(ef)) {
  1248. ef(dom);
  1249. }
  1250. }, this);
  1251. },
  1252. _postFilterContent : function(/* DomNode|DomNode[]? */dom,/* Boolean? */
  1253. nonDestructive) {
  1254. // summary:
  1255. // filter the output after getting the content of the editing area
  1256. dom = dom || this.editNode;
  1257. if (this.contentDomPostFilters.length) {
  1258. if (nonDestructive && dom['cloneNode']) {
  1259. dom = dom.cloneNode(true);
  1260. }
  1261. dojo.forEach(this.contentDomPostFilters, function(ef) {
  1262. dom = ef(dom);
  1263. });
  1264. }
  1265. var ec = this.getNodeChildrenHtml(dom);
  1266. if (!ec.replace(/^(?:\s|\xA0)+/g, "").replace(/(?:\s|\xA0)+$/g, "").length) {
  1267. ec = "";
  1268. }
  1269. // if(dojo.isIE){
  1270. // //removing appended <P>&nbsp;</P> for IE
  1271. // ec = ec.replace(/(?:<p>&nbsp;</p>[\n\r]*)+$/i,"");
  1272. // }
  1273. dojo.forEach(this.contentPostFilters, function(ef) {
  1274. ec = ef(ec);
  1275. });
  1276. return ec;
  1277. },
  1278. _saveContent : function(/* Event */e) {
  1279. // summary:
  1280. // Saves the content in an onunload event if the editor has not been
  1281. // closed
  1282. var saveTextarea = dojo.byId("dijit._editor.RichText.savedContent");
  1283. saveTextarea.value += this._SEPARATOR + this.name + ":"
  1284. + this.getValue();
  1285. },
  1286. escapeXml : function(/* String */str, /* Boolean */noSingleQuotes) {
  1287. // summary:
  1288. // Adds escape sequences for special characters in XML: &<>"'
  1289. // Optionally skips escapes for single quotes
  1290. str = str.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(
  1291. />/gm, "&gt;").replace(/"/gm, "&quot;");
  1292. if (!noSingleQuotes) {
  1293. str = str.replace(/'/gm, "&#39;");
  1294. }
  1295. return str; // string
  1296. },
  1297. getNodeHtml : function(/* DomNode */node) {
  1298. switch (node.nodeType) {
  1299. case 1 : // element node
  1300. var output = '<' + node.tagName.toLowerCase();
  1301. if (dojo.isMoz) {
  1302. if (node.getAttribute('type') == '_moz') {
  1303. node.removeAttribute('type');
  1304. }
  1305. if (node.getAttribute('_moz_dirty') != undefined) {
  1306. node.removeAttribute('_moz_dirty');
  1307. }
  1308. }
  1309. // store the list of attributes and sort it to have the
  1310. // attributes appear in the dictionary order
  1311. var attrarray = [];
  1312. if (dojo.isIE) {
  1313. var s = node.outerHTML;
  1314. s = s.substr(0, s.indexOf('>'));
  1315. s = s.replace(/(?:['"])[^"']*\1/g, '');// to make the
  1316. // following
  1317. // regexp safe
  1318. var reg = /([^\s=]+)=/g;
  1319. var m, key;
  1320. while ((m = reg.exec(s)) != undefined) {
  1321. key = m[1];
  1322. if (key.substr(0, 3) != '_dj') {
  1323. if (key == 'src' || key == 'href') {
  1324. if (node.getAttribute('_djrealurl')) {
  1325. attrarray
  1326. .push([
  1327. key,
  1328. node
  1329. .getAttribute('_djrealurl')]);
  1330. continue;
  1331. }
  1332. }
  1333. if (key == 'class') {
  1334. attrarray.push([key, node.className]);
  1335. } else {
  1336. attrarray
  1337. .push([key, node.getAttribute(key)]);
  1338. }
  1339. }
  1340. }
  1341. } else {
  1342. var attr, i = 0, attrs = node.attributes;
  1343. while ((attr = attrs[i++])) {
  1344. // ignore all attributes starting with _dj which are
  1345. // internal temporary attributes used by the editor
  1346. if (attr.name.substr(0, 3) != '_dj' /*
  1347. * &&
  1348. * (attr.specified ==
  1349. * undefined ||
  1350. * attr.specified)
  1351. */) {
  1352. var v = attr.value;
  1353. if (attr.name == 'src' || attr.name == 'href') {
  1354. if (node.getAttribute('_djrealurl')) {
  1355. v = node.getAttribute('_djrealurl');
  1356. }
  1357. }
  1358. attrarray.push([attr.name, v]);
  1359. }
  1360. }
  1361. }
  1362. attrarray.sort(function(a, b) {
  1363. return a[0] < b[0]
  1364. ? -1
  1365. : (a[0] == b[0] ? 0 : 1);
  1366. });
  1367. i = 0;
  1368. while ((attr = attrarray[i++])) {
  1369. output += ' ' + attr[0] + '="' + attr[1] + '"';
  1370. }
  1371. if (node.childNodes.length) {
  1372. output += '>' + this.getNodeChildrenHtml(node) + '</'
  1373. + node.tagName.toLowerCase() + '>';
  1374. } else {
  1375. output += ' />';
  1376. }
  1377. break;
  1378. case 3 : // text
  1379. // FIXME:
  1380. var output = this.escapeXml(node.nodeValue, true);
  1381. break;
  1382. case 8 : // comment
  1383. // FIXME:
  1384. var output = '<!--' + this.escapeXml(node.nodeValue, true)
  1385. + '-->';
  1386. break;
  1387. default :
  1388. var output = "Element not recognized - Type: "
  1389. + node.nodeType + " Name: " + node.nodeName;
  1390. }
  1391. return output;
  1392. },
  1393. getNodeChildrenHtml : function(/* DomNode */dom) {
  1394. // summary: Returns the html content of a DomNode and children
  1395. var out = "";
  1396. if (!dom) {
  1397. return out;
  1398. }
  1399. var nodes = dom["childNodes"] || dom;
  1400. var i = 0;
  1401. var node;
  1402. while ((node = nodes[i++])) {
  1403. out += this.getNodeHtml(node);
  1404. }
  1405. return out; // String
  1406. },
  1407. close : function(/* Boolean */save, /* Boolean */force) {
  1408. // summary:
  1409. // Kills the editor and optionally writes back the modified contents
  1410. // to the
  1411. // element from which it originated.
  1412. // save:
  1413. // Whether or not to save the changes. If false, the changes are
  1414. // discarded.
  1415. // force:
  1416. if (this.isClosed) {
  1417. return false;
  1418. }
  1419. if (!arguments.length) {
  1420. save = true;
  1421. }
  1422. this._content = this.getValue();
  1423. var changed = (this.savedContent != this._content);
  1424. // line height is squashed for iframes
  1425. // FIXME: why was this here? if (this.iframe){
  1426. // this.domNode.style.lineHeight = null; }
  1427. if (this.interval) {
  1428. clearInterval(this.interval);
  1429. }
  1430. if (this.textarea) {
  1431. with (this.textarea.style) {
  1432. position = "";
  1433. left = top = "";
  1434. if (dojo.isIE) {
  1435. overflow = this.__overflow;
  1436. this.__overflow = null;
  1437. }
  1438. }
  1439. if (save) {
  1440. this.textarea.value = this._content;
  1441. } else {
  1442. this.textarea.value = this.savedContent;
  1443. }
  1444. dojo._destroyElement(this.domNode);
  1445. this.domNode = this.textarea;
  1446. } else {
  1447. if (save) {
  1448. // why we treat moz differently? comment out to fix #1061
  1449. // if(dojo.isMoz){
  1450. // var nc = dojo.doc.createElement("span");
  1451. // this.domNode.appendChild(nc);
  1452. // nc.innerHTML = this.editNode.innerHTML;
  1453. // }else{
  1454. // this.domNode.innerHTML = this._content;
  1455. // }
  1456. this.domNode.innerHTML = this._content;
  1457. } else {
  1458. this.domNode.innerHTML = this.savedContent;
  1459. }
  1460. }
  1461. dojo.removeClass(this.domNode, "RichTextEditable");
  1462. this.isClosed = true;
  1463. this.isLoaded = false;
  1464. // FIXME: is this always the right thing to do?
  1465. delete this.editNode;
  1466. if (this.window && this.window._frameElement) {
  1467. this.window._frameElement = null;
  1468. }
  1469. this.window = null;
  1470. this.document = null;
  1471. this.editingArea = null;
  1472. this.editorObject = null;
  1473. return changed; // Boolean: whether the content has been modified
  1474. },
  1475. destroyRendering : function() {
  1476. // summary: stub
  1477. },
  1478. destroy : function() {
  1479. this.destroyRendering();
  1480. if (!this.isClosed) {
  1481. this.close(false);
  1482. }
  1483. this.inherited("destroy", arguments);
  1484. // dijit._editor.RichText.superclass.destroy.call(this);
  1485. },
  1486. _fixContentForMoz : function(/* String */html) {
  1487. // summary:
  1488. // Moz can not handle strong/em tags correctly, convert them to b/i
  1489. html = html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2');
  1490. html = html.replace(/<(\/)?em([ \>])/gi, '<$1i$2');
  1491. return html; // String
  1492. },
  1493. _srcInImgRegex : /(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi,
  1494. _hrefInARegex : /(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi,
  1495. _preFixUrlAttributes : function(/* String */html) {
  1496. html = html.replace(this._hrefInARegex,
  1497. '$1$4$2$3$5$2 _djrealurl=$2$3$5$2');
  1498. html = html.replace(this._srcInImgRegex,
  1499. '$1$4$2$3$5$2 _djrealurl=$2$3$5$2');
  1500. return html; // String
  1501. }
  1502. });
  1503. }