SpellCheck.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  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["dojox.editor.plugins.SpellCheck"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.editor.plugins.SpellCheck"] = true;
  8. dojo.provide("dojox.editor.plugins.SpellCheck");
  9. dojo.require("dijit.form.TextBox");
  10. dojo.require("dijit.form.DropDownButton");
  11. dojo.require("dijit.TooltipDialog");
  12. dojo.require("dijit.form.MultiSelect");
  13. dojo.require("dojo.io.script");
  14. dojo.require("dijit.Menu");
  15. dojo.requireLocalization("dojox.editor.plugins", "SpellCheck", null, "ROOT,ar,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");
  16. dojo.experimental("dojox.editor.plugins.SpellCheck");
  17. dojo.declare("dojox.editor.plugins._spellCheckControl", [dijit._Widget, dijit._Templated], {
  18. // summary:
  19. // The widget that is used for the UI of the batch spelling check
  20. widgetsInTemplate: true,
  21. templateString:
  22. "<table class='dijitEditorSpellCheckTable'>" +
  23. "<tr><td colspan='3' class='alignBottom'><label for='${textId}' id='${textId}_label'>${unfound}</label>" +
  24. "<div class='dijitEditorSpellCheckBusyIcon' id='${id}_progressIcon'></div></td></tr>" +
  25. "<tr>" +
  26. "<td class='dijitEditorSpellCheckBox'><input dojoType='dijit.form.TextBox' required='false' intermediateChanges='true' " +
  27. "class='dijitEditorSpellCheckBox' dojoAttachPoint='unfoundTextBox' id='${textId}'/></td>" +
  28. "<td><button dojoType='dijit.form.Button' class='blockButton' dojoAttachPoint='skipButton'>${skip}</button></td>" +
  29. "<td><button dojoType='dijit.form.Button' class='blockButton' dojoAttachPoint='skipAllButton'>${skipAll}</button></td>" +
  30. "</tr>" +
  31. "<tr>" +
  32. "<td class='alignBottom'><label for='${selectId}'>${suggestions}</td></label>" +
  33. "<td colspan='2'><button dojoType='dijit.form.Button' class='blockButton' dojoAttachPoint='toDicButton'>${toDic}</button></td>" +
  34. "</tr>" +
  35. "<tr>" +
  36. "<td>" +
  37. "<select dojoType='dijit.form.MultiSelect' id='${selectId}' " +
  38. "class='dijitEditorSpellCheckBox listHeight' dojoAttachPoint='suggestionSelect'></select>" +
  39. "</td>" +
  40. "<td colspan='2'>" +
  41. "<button dojoType='dijit.form.Button' class='blockButton' dojoAttachPoint='replaceButton'>${replace}</button>" +
  42. "<div class='topMargin'><button dojoType='dijit.form.Button' class='blockButton' " +
  43. "dojoAttachPoint='replaceAllButton'>${replaceAll}</button><div>" +
  44. "</td>" +
  45. "</tr>" +
  46. "<tr>" +
  47. "<td><div class='topMargin'><button dojoType='dijit.form.Button' dojoAttachPoint='cancelButton'>${cancel}</button></div></td>" +
  48. "<td></td>" +
  49. "<td></td>" +
  50. "</tr>" +
  51. "</table>",
  52. /*************************************************************************/
  53. /** Framework Methods **/
  54. /*************************************************************************/
  55. constructor: function(){
  56. // Indicate if the textbox ignores the text change event of the textbox
  57. this.ignoreChange = false;
  58. // Indicate if the text of the textbox is changed or not
  59. this.isChanged = false;
  60. // Indicate if the dialog is open or not
  61. this.isOpen = false;
  62. // Indicate if the dialog can be closed
  63. this.closable = true;
  64. },
  65. postMixInProperties: function(){
  66. this.id = dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
  67. this.textId = this.id + "_textBox";
  68. this.selectId = this.id + "_select";
  69. },
  70. postCreate: function(){
  71. var select = this.suggestionSelect;
  72. // Customize multi-select to single select
  73. dojo.removeAttr(select.domNode, "multiple");
  74. select.addItems = function(/*Array*/ items){
  75. // summary:
  76. // Add items to the select widget
  77. // items:
  78. // An array of items be added to the select
  79. // tags:
  80. // public
  81. var _this = this;
  82. var o = null;
  83. if(items && items.length > 0){
  84. dojo.forEach(items, function(item, i){
  85. o = dojo.create("option", {innerHTML: item, value: item}, _this.domNode);
  86. if(i == 0){
  87. o.selected = true;
  88. }
  89. });
  90. }
  91. };
  92. select.removeItems = function(){
  93. // summary:
  94. // Remove all the items within the select widget
  95. // tags:
  96. // public
  97. dojo.empty(this.domNode);
  98. };
  99. select.deselectAll = function(){
  100. // summary:
  101. // De-select all the selected items
  102. // tags:
  103. // public
  104. this.containerNode.selectedIndex = -1;
  105. };
  106. // Connect up all the controls with their event handler
  107. this.connect(this, "onKeyPress", "_cancel");
  108. this.connect(this.unfoundTextBox, "onKeyPress", "_enter");
  109. this.connect(this.unfoundTextBox, "onChange", "_unfoundTextBoxChange");
  110. this.connect(this.suggestionSelect, "onKeyPress", "_enter");
  111. this.connect(this.skipButton, "onClick", "onSkip");
  112. this.connect(this.skipAllButton, "onClick", "onSkipAll");
  113. this.connect(this.toDicButton, "onClick", "onAddToDic");
  114. this.connect(this.replaceButton, "onClick", "onReplace");
  115. this.connect(this.replaceAllButton, "onClick", "onReplaceAll");
  116. this.connect(this.cancelButton, "onClick", "onCancel");
  117. },
  118. /*************************************************************************/
  119. /** Public Methods **/
  120. /*************************************************************************/
  121. onSkip: function(){
  122. // Stub for the click event of the skip button.
  123. },
  124. onSkipAll: function(){
  125. // Stub for the click event of the skipAll button.
  126. },
  127. onAddToDic: function(){
  128. // Stub for the click event of the toDic button.
  129. },
  130. onReplace: function(){
  131. // Stub for the click event of the replace button.
  132. },
  133. onReplaceAll: function(){
  134. // Stub for the click event of the replaceAll button.
  135. },
  136. onCancel: function(){
  137. // Stub for the click event of the cancel button.
  138. },
  139. onEnter: function(){
  140. // Stub for the enter event of the unFound textbox.
  141. },
  142. focus: function(){
  143. // summary:
  144. // Set the focus of the control
  145. // tags:
  146. // public
  147. this.unfoundTextBox.focus();
  148. },
  149. /*************************************************************************/
  150. /** Private Methods **/
  151. /*************************************************************************/
  152. _cancel: function(/*Event*/ evt){
  153. // summary:
  154. // Handle the cancel event
  155. // evt:
  156. // The event object
  157. // tags:
  158. // private
  159. if(evt.keyCode == dojo.keys.ESCAPE){
  160. this.onCancel();
  161. dojo.stopEvent(evt);
  162. }
  163. },
  164. _enter: function(/*Event*/ evt){
  165. // summary:
  166. // Handle the enter event
  167. // evt:
  168. // The event object
  169. // tags:
  170. // private
  171. if(evt.keyCode == dojo.keys.ENTER){
  172. this.onEnter();
  173. dojo.stopEvent(evt);
  174. }
  175. },
  176. _unfoundTextBoxChange: function(){
  177. // summary:
  178. // Indicate that the Not Found textbox is changed or not
  179. // tags:
  180. // private
  181. var id = this.textId + "_label";
  182. if(!this.ignoreChange){
  183. dojo.byId(id).innerHTML = this["replaceWith"];
  184. this.isChanged = true;
  185. this.suggestionSelect.deselectAll();
  186. }else{
  187. dojo.byId(id).innerHTML = this["unfound"];
  188. }
  189. },
  190. _setUnfoundWordAttr: function(/*String*/ value){
  191. // summary:
  192. // Set the value of the Not Found textbox
  193. // value:
  194. // The value of the Not Found textbox
  195. // tags:
  196. // private
  197. value = value || "";
  198. this.unfoundTextBox.set("value", value);
  199. },
  200. _getUnfoundWordAttr: function(){
  201. // summary:
  202. // Get the value of the Not Found textbox
  203. // tags:
  204. // private
  205. return this.unfoundTextBox.get("value");
  206. },
  207. _setSuggestionListAttr: function(/*Array*/ values){
  208. // summary:
  209. // Set the items of the suggestion list
  210. // values:
  211. // The list of the suggestion items
  212. // tags:
  213. // private
  214. var select = this.suggestionSelect;
  215. values = values || [];
  216. select.removeItems();
  217. select.addItems(values);
  218. },
  219. _getSelectedWordAttr: function(){
  220. // summary:
  221. // Get the suggested word.
  222. // If the select box is selected, the value is the selected item's value,
  223. // else the value the the textbox's value
  224. // tags:
  225. // private
  226. var selected = this.suggestionSelect.getSelected();
  227. if(selected && selected.length > 0){
  228. return selected[0].value;
  229. }else{
  230. return this.unfoundTextBox.get("value");
  231. }
  232. },
  233. _setDisabledAttr: function(/*Boolean*/ disabled){
  234. // summary:
  235. // Enable/disable the control
  236. // tags:
  237. // private
  238. this.skipButton.set("disabled", disabled);
  239. this.skipAllButton.set("disabled", disabled);
  240. this.toDicButton.set("disabled", disabled);
  241. this.replaceButton.set("disabled", disabled);
  242. this.replaceAllButton.set("disabled", disabled);
  243. },
  244. _setInProgressAttr: function(/*Boolean*/ show){
  245. // summary:
  246. // Set the visibility of the progress icon
  247. // tags:
  248. // private
  249. var id = this.id + "_progressIcon",
  250. cmd = show ? "removeClass" : "addClass";
  251. dojo[cmd](id, "hidden");
  252. }
  253. });
  254. dojo.declare("dojox.editor.plugins._SpellCheckScriptMultiPart", null, {
  255. // summary:
  256. // It is a base network service component. It transfers text to a remote service port
  257. // with cross domain ability enabled. It can split text into specified pieces and send
  258. // them out one by one so that it can handle the case when the service has a limitation of
  259. // the capability.
  260. // The encoding is UTF-8.
  261. // ACTION [public const] String
  262. // Actions for the server-side piece to take
  263. ACTION_QUERY: "query",
  264. ACTION_UPDATE: "update",
  265. // callbackHandle [public] String
  266. // The callback name of JSONP
  267. callbackHandle: "callback",
  268. // maxBufferLength [public] Number
  269. // The max number of charactors that send to the service at one time.
  270. maxBufferLength: 100,
  271. // delimiter [public] String
  272. // A token that is used to identify the end of a word (a complete unit). It prevents the service from
  273. // cutting a single word into two parts. For example:
  274. // "Dojo toolkit is a ajax framework. It helps the developers buid their web applications."
  275. // Without the delimiter, the sentence might be split into the follow pieces which is absolutely
  276. // not the result we want.
  277. // "Dojo toolkit is a ajax fram", "ework It helps the developers bu", "id their web applications"
  278. // Having " " as the delimiter, we get the following correct pieces.
  279. // "Dojo toolkit is a ajax framework", " It helps the developers buid", " their web applications"
  280. delimiter: " ",
  281. // label [public] String
  282. // The leading label of the JSON response. The service will return the result like this:
  283. // {response: [
  284. // {
  285. // text: "teest",
  286. // suggestion: ["test","treat"]
  287. // }
  288. // ]}
  289. label: "response",
  290. // _timeout [private] Number
  291. // Set JSONP timeout period
  292. _timeout: 30000,
  293. SEC: 1000,
  294. constructor: function(){
  295. // The URL of the target service
  296. this.serviceEndPoint = "";
  297. // The queue that holds all the xhr request
  298. this._queue = [];
  299. // Indicate if the component is still working. For example, waiting for collecting all
  300. // the responses from the service
  301. this.isWorking = false;
  302. // The extra command passed to the service
  303. this.exArgs = null;
  304. // The counter that indicate if all the responses are collected to
  305. // assemble the final result.
  306. this._counter = 0;
  307. },
  308. send: function(/*String*/ content, /*String?*/ action){
  309. // summary:
  310. // Send the content to the service port with the specified action
  311. // content:
  312. // The text to be sent
  313. // action:
  314. // The action the service should take. Current support actions are
  315. // ACTION_QUERY and ACTION_UPDATE
  316. // tags:
  317. // public
  318. var _this = this,
  319. dt = this.delimiter,
  320. mbl = this.maxBufferLength,
  321. label = this.label,
  322. serviceEndPoint = this.serviceEndPoint,
  323. callbackParamName = this.callbackHandle,
  324. comms = this.exArgs,
  325. timeout = this._timeout,
  326. l = 0, r = 0;
  327. // Temparary list that holds the result returns from the service, which will be
  328. // assembled into a completed one.
  329. if(!this._result) {
  330. this._result = [];
  331. }
  332. action = action || this.ACTION_QUERY;
  333. var batchSend = function(){
  334. var plan = [];
  335. var plannedSize = 0;
  336. if(content && content.length > 0){
  337. _this.isWorking = true;
  338. var len = content.length;
  339. do{
  340. l = r + 1;
  341. if((r += mbl) > len){
  342. r = len;
  343. }else{
  344. // If there is no delimiter (emplty string), leave the right boundary where it is.
  345. // Else extend the right boundary to the first occurance of the delimiter if
  346. // it doesn't meet the end of the content.
  347. while(dt && content.charAt(r) != dt && r <= len){
  348. r++;
  349. }
  350. }
  351. // Record the information of the text slices
  352. plan.push({l: l, r: r});
  353. plannedSize++;
  354. }while(r < len);
  355. dojo.forEach(plan, function(item, index){
  356. var jsonpArgs = {
  357. url: serviceEndPoint,
  358. action: action,
  359. timeout: timeout,
  360. callbackParamName: callbackParamName,
  361. handle: function(response, ioArgs){
  362. if(++_this._counter <= this.size && !(response instanceof Error) &&
  363. response[label] && dojo.isArray(response[label])){
  364. // Collect the results
  365. var offset = this.offset;
  366. dojo.forEach(response[label], function(item){
  367. item.offset += offset;
  368. });
  369. // Put the packages in order
  370. _this._result[this.number]= response[label];
  371. }
  372. if(_this._counter == this.size){
  373. _this._finalizeCollection(this.action);
  374. _this.isWorking = false;
  375. if(_this._queue.length > 0){
  376. // Call the next request waiting in queue
  377. (_this._queue.shift())();
  378. }
  379. }
  380. }
  381. };
  382. jsonpArgs.content = comms ? dojo.mixin(comms, {action: action, content: content.substring(item.l - 1, item.r)}):
  383. {action: action, content: content.substring(item.l - 1, item.r)};
  384. jsonpArgs.size = plannedSize;
  385. jsonpArgs.number = index; // The index of the current package
  386. jsonpArgs.offset = item.l - 1;
  387. dojo.io.script.get(jsonpArgs);
  388. });
  389. }
  390. };
  391. if(!_this.isWorking){
  392. batchSend();
  393. }else{
  394. _this._queue.push(batchSend);
  395. }
  396. },
  397. _finalizeCollection: function(action){
  398. // summary:
  399. // Assemble the responses into one result.
  400. // action:
  401. // The action token
  402. // tags:
  403. // private
  404. var result = this._result,
  405. len = result.length;
  406. // Turn the result into a one-dimensional array
  407. for(var i = 0; i < len; i++){
  408. var temp = result.shift();
  409. result = result.concat(temp);
  410. }
  411. if(action == this.ACTION_QUERY){
  412. this.onLoad(result);
  413. }
  414. this._counter = 0;
  415. this._result = [];
  416. },
  417. onLoad: function(/*String*/ data){
  418. // Stub method for a sucessful call
  419. },
  420. setWaitingTime: function(/*Number*/ seconds){
  421. this._timeout = seconds * this.SEC;
  422. }
  423. });
  424. dojo.declare("dojox.editor.plugins.SpellCheck", [dijit._editor._Plugin], {
  425. // summary:
  426. // This plugin provides a spelling check cabability for the editor.
  427. // url [public] String
  428. // The url of the spelling check service
  429. url: "",
  430. // bufferLength [public] Number
  431. // The max length of each XHR request. It is used to divide the large
  432. // text into pieces so that the server-side piece can hold.
  433. bufferLength: 100,
  434. // interactive [public] Boolean
  435. // Indicate if the interactive spelling check is enabled
  436. interactive: false,
  437. // timeout [public] Number
  438. // The minutes to waiting for the response. The default value is 30 seconds.
  439. timeout: 30,
  440. // button [protected] dijit.form.DropDownButton
  441. // The button displayed on the editor's toolbar
  442. button: null,
  443. // _editor [private] dijit.Editor
  444. // The reference to the editor the plug-in belongs to.
  445. _editor: null,
  446. // exArgs [private] Object
  447. // The object that holds all the parametes passed into the constructor
  448. exArgs: null,
  449. // _cursorSpan [private] String
  450. // The span that holds the current position of the cursor
  451. _cursorSpan:
  452. "<span class=\"cursorPlaceHolder\"></span>",
  453. // _cursorSelector [private] String
  454. // The CSS selector of the cursor span
  455. _cursorSelector:
  456. "cursorPlaceHolder",
  457. // _incorrectWordsSpan [private] String
  458. // The wrapper that marks the incorrect words
  459. _incorrectWordsSpan:
  460. "<span class='incorrectWordPlaceHolder'>${text}</span>",
  461. // _ignoredIncorrectStyle [private] Object
  462. // The style of the ignored incorrect words
  463. _ignoredIncorrectStyle:
  464. {"cursor": "inherit", "borderBottom": "none", "backgroundColor": "transparent"},
  465. // _normalIncorrectStyle [private] Object
  466. // The style of the marked incorrect words.
  467. _normalIncorrectStyle:
  468. {"cursor": "pointer", "borderBottom": "1px dotted red", "backgroundColor": "yellow"},
  469. // _highlightedIncorrectStyle [private] Object
  470. // The style of the highlighted incorrect words
  471. _highlightedIncorrectStyle:
  472. {"borderBottom": "1px dotted red", "backgroundColor": "#b3b3ff"},
  473. // _selector [private] String
  474. // An empty CSS class that identifies the incorrect words
  475. _selector: "incorrectWordPlaceHolder",
  476. // _maxItemNumber [private] Number
  477. // The max number of the suggestion list items
  478. _maxItemNumber: 3,
  479. /*************************************************************************/
  480. /** Framework Methods **/
  481. /*************************************************************************/
  482. constructor: function(){
  483. // A list that holds all the spans that contains the incorrect words
  484. // It is used to select/replace the specified word.
  485. this._spanList = [];
  486. // The cache that stores all the words. It looks like the following
  487. // {
  488. // "word": [],
  489. // "wrd": ["word", "world"]
  490. // }
  491. this._cache = {};
  492. // Indicate if this plugin is enabled or not
  493. this._enabled = true;
  494. // The index of the _spanList
  495. this._iterator = 0;
  496. },
  497. setEditor: function(/*dijit.Editor*/ editor){
  498. this._editor = editor;
  499. this._initButton();
  500. this._setNetwork();
  501. this._connectUp();
  502. },
  503. /*************************************************************************/
  504. /** Private Methods **/
  505. /*************************************************************************/
  506. _initButton: function(){
  507. // summary:
  508. // Initialize the button displayed on the editor's toolbar
  509. // tags:
  510. // private
  511. var _this = this,
  512. strings = this._strings = dojo.i18n.getLocalization("dojox.editor.plugins", "SpellCheck"),
  513. dialogPane = this._dialog = new dijit.TooltipDialog();
  514. dialogPane.set("content", (this._dialogContent = new dojox.editor.plugins._spellCheckControl({
  515. unfound: strings["unfound"],
  516. skip: strings["skip"],
  517. skipAll: strings["skipAll"],
  518. toDic: strings["toDic"],
  519. suggestions: strings["suggestions"],
  520. replaceWith: strings["replaceWith"],
  521. replace: strings["replace"],
  522. replaceAll: strings["replaceAll"],
  523. cancel: strings["cancel"]
  524. })));
  525. this.button = new dijit.form.DropDownButton({
  526. label: strings["widgetLabel"],
  527. showLabel: false,
  528. iconClass: "dijitEditorSpellCheckIcon",
  529. dropDown: dialogPane,
  530. id: dijit.getUniqueId(this.declaredClass.replace(/\./g,"_")) + "_dialogPane",
  531. closeDropDown: function(focus){
  532. // Determine if the dialog can be closed
  533. if(_this._dialogContent.closable){
  534. _this._dialogContent.isOpen = false;
  535. if(dojo.isIE){
  536. var pos = _this._iterator,
  537. list = _this._spanList;
  538. if(pos < list.length && pos >=0 ){
  539. dojo.style(list[pos], _this._normalIncorrectStyle);
  540. }
  541. }
  542. if(this._opened){
  543. dijit.popup.close(this.dropDown);
  544. if(focus){ this.focus(); }
  545. this._opened = false;
  546. this.state = "";
  547. }
  548. }
  549. }
  550. });
  551. _this._dialogContent.isOpen = false;
  552. dijit.setWaiState(dialogPane.domNode, "label", this._strings["widgetLabel"]);
  553. },
  554. _setNetwork: function(){
  555. // summary:
  556. // Set up the underlying network service
  557. // tags:
  558. // private
  559. var comms = this.exArgs;
  560. if(!this._service){
  561. var service = this._service = new dojox.editor.plugins._SpellCheckScriptMultiPart();
  562. service.serviceEndPoint = this.url;
  563. service.maxBufferLength = this.bufferLength;
  564. service.setWaitingTime(this.timeout);
  565. // Pass the other arguments directly to the service
  566. if(comms){
  567. delete comms.name;
  568. delete comms.url;
  569. delete comms.interactive;
  570. delete comms.timeout;
  571. service.exArgs = comms;
  572. }
  573. }
  574. },
  575. _connectUp: function(){
  576. // summary:
  577. // Connect up all the events with their event handlers
  578. // tags:
  579. // private
  580. var editor = this._editor,
  581. cont = this._dialogContent;
  582. this.connect(this.button, "set", "_disabled");
  583. this.connect(this._service, "onLoad", "_loadData");
  584. this.connect(this._dialog, "onOpen", "_openDialog");
  585. this.connect(editor, "onKeyPress", "_keyPress");
  586. this.connect(editor, "onLoad", "_submitContent");
  587. this.connect(cont, "onSkip", "_skip");
  588. this.connect(cont, "onSkipAll", "_skipAll");
  589. this.connect(cont, "onAddToDic", "_add");
  590. this.connect(cont, "onReplace", "_replace");
  591. this.connect(cont, "onReplaceAll", "_replaceAll");
  592. this.connect(cont, "onCancel", "_cancel");
  593. this.connect(cont, "onEnter", "_enter");
  594. editor.contentPostFilters.push(this._spellCheckFilter); // Register the filter
  595. dojo.publish(dijit._scopeName + ".Editor.plugin.SpellCheck.getParser", [this]); // Get the language parser
  596. if(!this.parser){
  597. console.error("Can not get the word parser!");
  598. }
  599. },
  600. /*************************************************************************/
  601. /** Event Handlers **/
  602. /*************************************************************************/
  603. _disabled: function(name, disabled){
  604. // summary:
  605. // When the plugin is disabled (the button is disabled), reset all to their initial status.
  606. // If the interactive mode is on, check the content once it is enabled.
  607. // name:
  608. // Command name
  609. // disabled:
  610. // Command argument
  611. // tags:
  612. // private
  613. if(name == "disabled"){
  614. if(disabled){
  615. this._iterator = 0;
  616. this._spanList = [];
  617. }else if(this.interactive && !disabled && this._service){
  618. this._submitContent(true);
  619. }
  620. this._enabled = !disabled;
  621. }
  622. },
  623. _keyPress: function(evt){
  624. // summary:
  625. // The handler of the onKeyPress event of the editor
  626. // tags:
  627. // private
  628. if(this.interactive){
  629. var v = 118, V = 86,
  630. cc = evt.charCode;
  631. if(!evt.altKey && cc == dojo.keys.SPACE){
  632. this._submitContent();
  633. }else if((evt.ctrlKey && (cc == v || cc == V)) || (!evt.ctrlKey && evt.charCode)){
  634. this._submitContent(true);
  635. }
  636. }
  637. },
  638. _loadData: function(/*Array*/ data){
  639. // summary:
  640. // Apply the query result to the content
  641. // data:
  642. // The result of the query
  643. // tags:
  644. // private
  645. var cache = this._cache,
  646. html = this._editor.get("value"),
  647. cont = this._dialogContent;
  648. this._iterator = 0;
  649. // Update the local cache
  650. dojo.forEach(data, function(d){
  651. cache[d.text] = d.suggestion;
  652. cache[d.text].correct = false;
  653. });
  654. if(this._enabled){
  655. // Mark incorrect words
  656. cont.closable = false;
  657. this._markIncorrectWords(html, cache);
  658. cont.closable = true;
  659. if(this._dialogContent.isOpen){
  660. this._iterator = -1;
  661. this._skip();
  662. }
  663. }
  664. },
  665. _openDialog: function(){
  666. // summary:
  667. // The handler of the onOpen event
  668. var cont = this._dialogContent;
  669. // Clear dialog content and disable it first
  670. cont.ignoreChange = true;
  671. cont.set("unfoundWord", "");
  672. cont.set("suggestionList", null);
  673. cont.set("disabled", true);
  674. cont.set("inProgress", true);
  675. cont.isOpen = true; // Indicate that the dialog is open
  676. cont.closable = false;
  677. this._submitContent();
  678. cont.closable = true;
  679. },
  680. _skip: function(/*Event?*/ evt, /*Boolean?*/ noUpdate){
  681. // summary:
  682. // Ignore this word and move to the next unignored one.
  683. // evt:
  684. // The event object
  685. // noUpdate:
  686. // Indicate whether to update the status of the span list or not
  687. // tags:
  688. // private
  689. var cont = this._dialogContent,
  690. list = this._spanList || [],
  691. len = list.length,
  692. iter = this._iterator;
  693. cont.closable = false;
  694. cont.isChanged = false;
  695. cont.ignoreChange = true;
  696. // Skip the current word
  697. if(!noUpdate && iter >= 0 && iter < len){
  698. this._skipWord(iter);
  699. }
  700. // Move to the next
  701. while(++iter < len && list[iter].edited == true){ /* do nothing */}
  702. if(iter < len){
  703. this._iterator = iter;
  704. this._populateDialog(iter);
  705. this._selectWord(iter);
  706. }else{
  707. // Reaches the end of the list
  708. this._iterator = -1;
  709. cont.set("unfoundWord", this._strings["msg"]);
  710. cont.set("suggestionList", null);
  711. cont.set("disabled", true);
  712. cont.set("inProgress", false);
  713. }
  714. setTimeout(function(){
  715. // When moving the focus out of the iframe in WebKit browsers, we
  716. // need to focus something else first. So the textbox
  717. // can be focused correctly.
  718. if(dojo.isWebKit) { cont.skipButton.focus(); }
  719. cont.focus();
  720. cont.ignoreChange = false;
  721. cont.closable = true;
  722. }, 0);
  723. },
  724. _skipAll: function(){
  725. // summary:
  726. // Ignore all the same words
  727. // tags:
  728. // private
  729. this._dialogContent.closable = false;
  730. this._skipWordAll(this._iterator);
  731. this._skip();
  732. },
  733. _add: function(){
  734. // summary:
  735. // Add the unrecognized word into the dictionary
  736. // tags:
  737. // private
  738. var cont = this._dialogContent;
  739. cont.closable = false;
  740. cont.isOpen = true;
  741. this._addWord(this._iterator, cont.get("unfoundWord"));
  742. this._skip();
  743. },
  744. _replace: function(){
  745. // summary:
  746. // Replace the incorrect word with the selected one,
  747. // or the one the user types in the textbox
  748. // tags:
  749. // private
  750. var cont = this._dialogContent,
  751. iter = this._iterator,
  752. targetWord = cont.get("selectedWord");
  753. cont.closable = false;
  754. this._replaceWord(iter, targetWord);
  755. this._skip(null, true);
  756. },
  757. _replaceAll: function(){
  758. // summary:
  759. // Replace all the words with the same text
  760. // tags:
  761. // private
  762. var cont = this._dialogContent,
  763. list = this._spanList,
  764. len = list.length,
  765. word = list[this._iterator].innerHTML.toLowerCase(),
  766. targetWord = cont.get("selectedWord");
  767. cont.closable = false;
  768. for(var iter = 0; iter < len; iter++){
  769. // If this word is not ignored and is the same as the source word,
  770. // replace it.
  771. if(list[iter].innerHTML.toLowerCase() == word){
  772. this._replaceWord(iter, targetWord);
  773. }
  774. }
  775. this._skip(null, true);
  776. },
  777. _cancel: function(){
  778. // summary:
  779. // Cancel this check action
  780. // tags:
  781. // private
  782. this._dialogContent.closable = true;
  783. this._editor.focus();
  784. },
  785. _enter: function(){
  786. // summary:
  787. // Handle the ENTER event
  788. // tags:
  789. // private
  790. if(this._dialogContent.isChanged){
  791. this._replace();
  792. }else{
  793. this._skip();
  794. }
  795. },
  796. /*************************************************************************/
  797. /** Utils **/
  798. /*************************************************************************/
  799. _query: function(/*String*/ html){
  800. // summary:
  801. // Send the query text to the service. The query text is a string of words
  802. // separated by space.
  803. // html:
  804. // The html value of the editor
  805. // tags:
  806. // private
  807. var service = this._service,
  808. cache = this._cache,
  809. words = this.parser.parseIntoWords(this._html2Text(html)) || [];
  810. var content = [];
  811. dojo.forEach(words, function(word){
  812. word = word.toLowerCase();
  813. if(!cache[word]){
  814. // New word that need to be send to the server side for check
  815. cache[word] = [];
  816. cache[word].correct = true;
  817. content.push(word);
  818. }
  819. });
  820. if(content.length > 0){
  821. service.send(content.join(" "));
  822. }else if(!service.isWorking){
  823. this._loadData([]);
  824. }
  825. },
  826. _html2Text: function(html){
  827. // summary:
  828. // Substitute the tag with white charactors so that the server
  829. // can easily process the text. For example:
  830. // "<a src="sample.html">Hello, world!</a>" ==>
  831. // " Hello, world! "
  832. // html:
  833. // The html code
  834. // tags:
  835. // private
  836. var text = [],
  837. isTag = false,
  838. len = html ? html.length : 0;
  839. for(var i = 0; i < len; i++){
  840. if(html.charAt(i) == "<"){ isTag = true; }
  841. if(isTag == true){
  842. text.push(" ");
  843. }else{
  844. text.push(html.charAt(i));
  845. }
  846. if(html.charAt(i) == ">"){ isTag = false; }
  847. }
  848. return text.join("");
  849. },
  850. _getBookmark: function(/*String*/ eValue){
  851. // summary:
  852. // Get the cursor position. It is the index of the characters
  853. // where the cursor is.
  854. // eValue:
  855. // The html value of the editor
  856. // tags:
  857. // private
  858. var ed = this._editor,
  859. cp = this._cursorSpan;
  860. ed.execCommand("inserthtml", cp);
  861. var nv = ed.get("value"),
  862. index = nv.indexOf(cp),
  863. i = -1;
  864. while(++i < index && eValue.charAt(i) == nv.charAt(i)){ /* do nothing */}
  865. return i;
  866. },
  867. _moveToBookmark: function(){
  868. // summary:
  869. // Move to the position when the cursor was.
  870. // tags:
  871. // private
  872. var ed = this._editor,
  873. cps = dojo.withGlobal(ed.window, "query", dojo, ["." + this._cursorSelector]),
  874. cursorSpan = cps && cps[0];
  875. // Find the cursor place holder
  876. if(cursorSpan){
  877. ed._sCall("selectElement", [cursorSpan]);
  878. ed._sCall("collapse", [true]);
  879. var parent = cursorSpan.parentNode;
  880. if(parent){ parent.removeChild(cursorSpan); }
  881. }
  882. },
  883. _submitContent: function(/*Boolean?*/ delay){
  884. // summary:
  885. // Functions to submit the content of the editor
  886. // delay:
  887. // Indicate if the action is taken immediately or not
  888. // tags:
  889. // private
  890. if(delay){
  891. var _this = this,
  892. interval = 3000;
  893. if(this._delayHandler){
  894. clearTimeout(this._delayHandler);
  895. this._delayHandler = null;
  896. }
  897. setTimeout(function(){ _this._query(_this._editor.get("value")); }, interval);
  898. }else{
  899. this._query(this._editor.get("value"));
  900. }
  901. },
  902. _populateDialog: function(index){
  903. // summary:
  904. // Populate the content of the dailog
  905. // index:
  906. // The idex of the span list
  907. // tags:
  908. // private
  909. var list = this._spanList,
  910. cache = this._cache,
  911. cont = this._dialogContent;
  912. cont.set("disabled", false);
  913. if(index < list.length && list.length > 0){
  914. var word = list[index].innerHTML;
  915. cont.set("unfoundWord", word);
  916. cont.set("suggestionList", cache[word.toLowerCase()]);
  917. cont.set("inProgress", false);
  918. }
  919. },
  920. _markIncorrectWords: function(/*String*/ html, /*Object*/ cache){
  921. // summary:
  922. // Mark the incorrect words and set up menus if available
  923. // html:
  924. // The html value of the editor
  925. // cache:
  926. // The local word cache
  927. // tags:
  928. // private
  929. var _this = this,
  930. parser = this.parser,
  931. editor = this._editor,
  932. spanString = this._incorrectWordsSpan,
  933. nstyle = this._normalIncorrectStyle,
  934. selector = this._selector,
  935. words = parser.parseIntoWords(this._html2Text(html).toLowerCase()),
  936. indices = parser.getIndices(),
  937. bookmark = this._cursorSpan,
  938. bmpos = this._getBookmark(html),
  939. spanOffset = "<span class='incorrectWordPlaceHolder'>".length,
  940. bmMarked = false,
  941. cArray = html.split(""),
  942. spanList = null;
  943. // Mark the incorrect words and cursor position
  944. for(var i = words.length - 1; i >= 0; i--){
  945. var word = words[i];
  946. if(cache[word] && !cache[word].correct){
  947. var offset = indices[i],
  948. len = words[i].length,
  949. end = offset + len;
  950. if(end <= bmpos && !bmMarked){
  951. cArray.splice(bmpos, 0, bookmark);
  952. bmMarked = true;
  953. }
  954. cArray.splice(offset, len, dojo.string.substitute(spanString, {text: html.substring(offset, end)}));
  955. if(offset < bmpos && bmpos < end && !bmMarked){
  956. var tmp = cArray[offset].split("");
  957. tmp.splice(spanOffset + bmpos - offset, 0, bookmark);
  958. cArray[offset] = tmp.join("");
  959. bmMarked = true;
  960. }
  961. }
  962. }
  963. if(!bmMarked){
  964. cArray.splice(bmpos, 0, bookmark);
  965. bmMarked = true;
  966. }
  967. editor.set("value", cArray.join(""));
  968. editor._cursorToStart = false; // HACK! But really necessary here.
  969. this._moveToBookmark();
  970. // Get the incorrect words <span>
  971. spanList = this._spanList = dojo.withGlobal(editor.window, "query", dojo, ["." + this._selector]);
  972. dojo.forEach(spanList, function(span, i){ span.id = selector + i; });
  973. // Set them to the incorrect word style
  974. if(!this.interactive){ delete nstyle.cursor; }
  975. spanList.style(nstyle);
  976. if(this.interactive){
  977. // Build the context menu
  978. if(_this._contextMenu){
  979. _this._contextMenu.uninitialize();
  980. _this._contextMenu = null;
  981. }
  982. _this._contextMenu = new dijit.Menu({
  983. targetNodeIds: [editor.iframe],
  984. bindDomNode: function(/*String|DomNode*/ node){
  985. // summary:
  986. // Attach menu to given node
  987. node = dojo.byId(node);
  988. var cn; // Connect node
  989. // Support context menus on iframes. Rather than binding to the iframe itself we need
  990. // to bind to the <body> node inside the iframe.
  991. var iframe, win;
  992. if(node.tagName.toLowerCase() == "iframe"){
  993. iframe = node;
  994. win = this._iframeContentWindow(iframe);
  995. cn = dojo.withGlobal(win, dojo.body);
  996. }else{
  997. // To capture these events at the top level, attach to <html>, not <body>.
  998. // Otherwise right-click context menu just doesn't work.
  999. cn = (node == dojo.body() ? dojo.doc.documentElement : node);
  1000. }
  1001. // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
  1002. var binding = {
  1003. node: node,
  1004. iframe: iframe
  1005. };
  1006. // Save info about binding in _bindings[], and make node itself record index(+1) into
  1007. // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
  1008. // start with a number, which fails on FF/safari.
  1009. dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding));
  1010. // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
  1011. // loading yet, in which case we need to wait for the onload event first, and then connect
  1012. // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so
  1013. // we need to monitor keyboard events in addition to the oncontextmenu event.
  1014. var doConnects = dojo.hitch(this, function(cn){
  1015. return [
  1016. // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
  1017. // rather than shift-F10?
  1018. dojo.connect(cn, this.leftClickToOpen ? "onclick" : "oncontextmenu", this, function(evt){
  1019. var target = evt.target,
  1020. strings = _this._strings;
  1021. // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
  1022. if(dojo.hasClass(target, selector) && !target.edited){ // Click on the incorrect word
  1023. dojo.stopEvent(evt);
  1024. // Build the on-demand menu items
  1025. var maxNumber = _this._maxItemNumber,
  1026. id = target.id,
  1027. index = id.substring(selector.length),
  1028. suggestions = cache[target.innerHTML.toLowerCase()],
  1029. slen = suggestions.length;
  1030. // Add the suggested words menu items
  1031. this.destroyDescendants();
  1032. if(slen == 0){
  1033. this.addChild(new dijit.MenuItem({
  1034. label: strings["iMsg"],
  1035. disabled: true
  1036. }));
  1037. }else{
  1038. for(var i = 0 ; i < maxNumber && i < slen; i++){
  1039. this.addChild(new dijit.MenuItem({
  1040. label: suggestions[i],
  1041. onClick: (function(){
  1042. var idx = index, txt = suggestions[i];
  1043. return function(){
  1044. _this._replaceWord(idx, txt);
  1045. editor.focus();
  1046. };
  1047. })()
  1048. }));
  1049. }
  1050. }
  1051. //Add the other action menu items
  1052. this.addChild(new dijit.MenuSeparator());
  1053. this.addChild(new dijit.MenuItem({
  1054. label: strings["iSkip"],
  1055. onClick: function(){
  1056. _this._skipWord(index);
  1057. editor.focus();
  1058. }
  1059. }));
  1060. this.addChild(new dijit.MenuItem({
  1061. label: strings["iSkipAll"],
  1062. onClick: function(){
  1063. _this._skipWordAll(index);
  1064. editor.focus();
  1065. }
  1066. }));
  1067. this.addChild(new dijit.MenuSeparator());
  1068. this.addChild(new dijit.MenuItem({
  1069. label: strings["toDic"],
  1070. onClick: function(){
  1071. _this._addWord(index);
  1072. editor.focus();
  1073. }
  1074. }));
  1075. this._scheduleOpen(target, iframe, {x: evt.pageX, y: evt.pageY});
  1076. }
  1077. }),
  1078. dojo.connect(cn, "onkeydown", this, function(evt){
  1079. if(evt.shiftKey && evt.keyCode == dojo.keys.F10){
  1080. dojo.stopEvent(evt);
  1081. this._scheduleOpen(evt.target, iframe); // no coords - open near target node
  1082. }
  1083. })
  1084. ];
  1085. });
  1086. binding.connects = cn ? doConnects(cn) : [];
  1087. if(iframe){
  1088. // Setup handler to [re]bind to the iframe when the contents are initially loaded,
  1089. // and every time the contents change.
  1090. // Need to do this b/c we are actually binding to the iframe's <body> node.
  1091. // Note: can't use dojo.connect(), see #9609.
  1092. binding.onloadHandler = dojo.hitch(this, function(){
  1093. // want to remove old connections, but IE throws exceptions when trying to
  1094. // access the <body> node because it's already gone, or at least in a state of limbo
  1095. var win = this._iframeContentWindow(iframe);
  1096. cn = dojo.withGlobal(win, dojo.body);
  1097. binding.connects = doConnects(cn);
  1098. });
  1099. if(iframe.addEventListener){
  1100. iframe.addEventListener("load", binding.onloadHandler, false);
  1101. }else{
  1102. iframe.attachEvent("onload", binding.onloadHandler);
  1103. }
  1104. }
  1105. }
  1106. });
  1107. }
  1108. },
  1109. _selectWord: function(index){
  1110. // summary:
  1111. // Select the incorrect word. Move to it and highlight it
  1112. // index:
  1113. // The index of the span list
  1114. // tags:
  1115. // private
  1116. var list = this._spanList,
  1117. win = this._editor.window;
  1118. if(index < list.length && list.length > 0){
  1119. dojo.withGlobal(win, "selectElement", dijit._editor.selection, [list[index]]);
  1120. dojo.withGlobal(win, "collapse", dijit._editor.selection, [true]);
  1121. this._findText(list[index].innerHTML, false, false);
  1122. if(dojo.isIE){
  1123. // Because the selection in the iframe will be lost when the outer window get the
  1124. // focus, we need to mimic the highlight ourselves.
  1125. dojo.style(list[index], this._highlightedIncorrectStyle);
  1126. }
  1127. }
  1128. },
  1129. _replaceWord: function(index, text){
  1130. // summary:
  1131. // Replace the word at the given index with the text
  1132. // index:
  1133. // The index of the span list
  1134. // text:
  1135. // The text to be replaced with
  1136. // tags:
  1137. // private
  1138. var list = this._spanList;
  1139. list[index].innerHTML = text;
  1140. dojo.style(list[index], this._ignoredIncorrectStyle);
  1141. list[index].edited = true;
  1142. },
  1143. _skipWord: function(index){
  1144. // summary:
  1145. // Skip the word at the index
  1146. // index:
  1147. // The index of the span list
  1148. // tags:
  1149. // private
  1150. var list = this._spanList;
  1151. dojo.style(list[index], this._ignoredIncorrectStyle);
  1152. this._cache[list[index].innerHTML.toLowerCase()].correct = true;
  1153. list[index].edited = true;
  1154. },
  1155. _skipWordAll: function(index, /*String?*/word){
  1156. // summary:
  1157. // Skip the all the word that have the same text as the word at the index
  1158. // or the given word
  1159. // index:
  1160. // The index of the span list
  1161. // word:
  1162. // If this argument is given, skip all the words that have the same text
  1163. // as the word
  1164. // tags:
  1165. // private
  1166. var list = this._spanList,
  1167. len = list.length;
  1168. word = word || list[index].innerHTML.toLowerCase();
  1169. for(var i = 0; i < len; i++){
  1170. if(!list[i].edited && list[i].innerHTML.toLowerCase() == word){
  1171. this._skipWord(i);
  1172. }
  1173. }
  1174. },
  1175. _addWord: function(index, /*String?*/word){
  1176. // summary:
  1177. // Add the word at the index to the dictionary
  1178. // index:
  1179. // The index of the span list
  1180. // word:
  1181. // If this argument is given, add the word to the dictionary and
  1182. // skip all the words like it
  1183. // tags:
  1184. // private
  1185. var service = this._service;
  1186. service.send(word || this._spanList[index].innerHTML.toLowerCase(), service.ACTION_UPDATE);
  1187. this._skipWordAll(index, word);
  1188. },
  1189. _findText: function(/*String*/ txt, /*Boolean*/ caseSensitive, /*Boolean*/ backwards){
  1190. // summary:
  1191. // This function invokes a find with specific options
  1192. // txt: String
  1193. // The text to locate in the document.
  1194. // caseSensitive: Boolean
  1195. // Whether or ot to search case-sensitively.
  1196. // backwards: Boolean
  1197. // Whether or not to search backwards in the document.
  1198. // tags:
  1199. // private.
  1200. // returns:
  1201. // Boolean indicating if the content was found or not.
  1202. var ed = this._editor,
  1203. win = ed.window,
  1204. found = false;
  1205. if(txt){
  1206. if(win.find){
  1207. found = win.find(txt, caseSensitive, backwards, false, false, false, false);
  1208. }else{
  1209. var doc = ed.document;
  1210. if(doc.selection){
  1211. /* IE */
  1212. // Focus to restore position/selection,
  1213. // then shift to search from current position.
  1214. this._editor.focus();
  1215. var txtRg = doc.body.createTextRange();
  1216. var curPos = doc.selection?doc.selection.createRange():null;
  1217. if(curPos){
  1218. if(backwards){
  1219. txtRg.setEndPoint("EndToStart", curPos);
  1220. }else{
  1221. txtRg.setEndPoint("StartToEnd", curPos);
  1222. }
  1223. }
  1224. var flags = caseSensitive?4:0;
  1225. if(backwards){
  1226. flags = flags | 1;
  1227. }
  1228. //flags = flags |
  1229. found = txtRg.findText(txt,txtRg.text.length,flags);
  1230. if(found){
  1231. txtRg.select();
  1232. }
  1233. }
  1234. }
  1235. }
  1236. return found;
  1237. },
  1238. _spellCheckFilter: function(/*String*/ value){
  1239. // summary:
  1240. // Filter out the incorrect word style so that the value of the edtior
  1241. // won't include the spans that wrap around the incorrect words
  1242. // value:
  1243. // The html value of the editor
  1244. // tags:
  1245. // private
  1246. var regText = /<span class=["']incorrectWordPlaceHolder["'].*?>(.*?)<\/span>/g;
  1247. return value.replace(regText, "$1");
  1248. }
  1249. });
  1250. // Register this plugin.
  1251. dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){
  1252. if(o.plugin){ return; }
  1253. var name = o.args.name.toLowerCase();
  1254. if(name === "spellcheck"){
  1255. o.plugin = new dojox.editor.plugins.SpellCheck({
  1256. url: ("url" in o.args) ? o.args.url : "",
  1257. interactive: ("interactive" in o.args) ? o.args.interactive : false,
  1258. bufferLength: ("bufferLength" in o.args) ? o.args.bufferLength: 100,
  1259. timeout: ("timeout" in o.args) ? o.args.timeout : 30,
  1260. exArgs: o.args
  1261. });
  1262. }
  1263. });
  1264. }