SpellCheck.js 42 KB

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