plugin.js 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877
  1. 'use strict';
  2. CKEDITOR.plugins.add('scayt', {
  3. //requires : ['menubutton', 'dialog'],
  4. requires: 'menubutton,dialog',
  5. lang: 'af,ar,bg,bn,bs,ca,cs,cy,da,de,el,en-au,en-ca,en-gb,en,eo,es,et,eu,fa,fi,fo,fr-ca,fr,gl,gu,he,hi,hr,hu,is,it,ja,ka,km,ko,lt,lv,mk,mn,ms,nb,nl,no,pl,pt-br,pt,ro,ru,sk,sl,sr-latn,sr,sv,th,tr,ug,uk,vi,zh-cn,zh', // %REMOVE_LINE_CORE%
  6. icons: 'scayt', // %REMOVE_LINE_CORE%
  7. hidpi: true, // %REMOVE_LINE_CORE%
  8. tabToOpen : null,
  9. dialogName: 'scaytDialog',
  10. init: function(editor) {
  11. var self = this,
  12. plugin = CKEDITOR.plugins.scayt;
  13. this.bindEvents(editor);
  14. this.parseConfig(editor);
  15. this.addRule(editor);
  16. // source mode
  17. CKEDITOR.dialog.add(this.dialogName, CKEDITOR.getUrl(this.path + 'dialogs/options.js'));
  18. // end source mode
  19. this.addMenuItems(editor);
  20. var config = editor.config,
  21. lang = editor.lang.scayt,
  22. env = CKEDITOR.env;
  23. editor.ui.add('Scayt', CKEDITOR.UI_MENUBUTTON, {
  24. label : lang.text_title,
  25. title : ( editor.plugins.wsc ? editor.lang.wsc.title : lang.text_title ),
  26. // SCAYT doesn't work in IE Compatibility Mode and IE (8 & 9) Quirks Mode
  27. modes : {wysiwyg: !(env.ie && ( env.version < 8 || env.quirks ) ) },
  28. toolbar: 'spellchecker,20',
  29. refresh: function() {
  30. var buttonState = editor.ui.instances.Scayt.getState();
  31. // check if scayt is created
  32. if(editor.scayt) {
  33. // check if scayt is enabled
  34. if(plugin.state.scayt[editor.name]) {
  35. buttonState = CKEDITOR.TRISTATE_ON;
  36. } else {
  37. buttonState = CKEDITOR.TRISTATE_OFF;
  38. }
  39. }
  40. editor.fire('scaytButtonState', buttonState);
  41. },
  42. onRender: function() {
  43. var that = this;
  44. editor.on('scaytButtonState', function(ev) {
  45. if(typeof ev.data !== undefined) {
  46. that.setState(ev.data);
  47. }
  48. });
  49. },
  50. onMenu : function() {
  51. var scaytInstance = editor.scayt;
  52. editor.getMenuItem('scaytToggle').label = editor.lang.scayt[(scaytInstance ? plugin.state.scayt[editor.name] : false) ? 'btn_disable' : 'btn_enable'];
  53. // If UI tab is disabled we shouldn't show menu item
  54. var menuDefinition = {
  55. scaytToggle : CKEDITOR.TRISTATE_OFF,
  56. scaytOptions : scaytInstance ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
  57. scaytLangs : scaytInstance ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
  58. scaytDict : scaytInstance ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
  59. scaytAbout : scaytInstance ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED,
  60. WSC : editor.plugins.wsc ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
  61. };
  62. if(!editor.config.scayt_uiTabs[0]) {
  63. delete menuDefinition.scaytOptions;
  64. }
  65. if(!editor.config.scayt_uiTabs[1]) {
  66. delete menuDefinition.scaytLangs;
  67. }
  68. if(!editor.config.scayt_uiTabs[2]) {
  69. delete menuDefinition.scaytDict;
  70. }
  71. return menuDefinition;
  72. }
  73. });
  74. // If the 'contextmenu' plugin is loaded, register the listeners.
  75. if(editor.contextMenu && editor.addMenuItems) {
  76. editor.contextMenu.addListener(function(element, selection) {
  77. var scaytInstance = editor.scayt,
  78. result, selectionNode;
  79. if(scaytInstance) {
  80. selectionNode = scaytInstance.getSelectionNode();
  81. // SCAYT shouldn't build context menu if instance isnot created or word is without misspelling or grammar problem
  82. if(selectionNode) {
  83. var items = self.menuGenerator(editor, selectionNode);
  84. scaytInstance.showBanner('.' + editor.contextMenu._.definition.panel.className.split(' ').join(' .'));
  85. result = items;
  86. }
  87. }
  88. return result;
  89. });
  90. editor.contextMenu._.onHide = CKEDITOR.tools.override(editor.contextMenu._.onHide, function(org) {
  91. return function() {
  92. var scaytInstance = editor.scayt;
  93. if(scaytInstance) {
  94. scaytInstance.hideBanner();
  95. }
  96. return org.apply(this);
  97. };
  98. });
  99. }
  100. },
  101. addMenuItems: function(editor) {
  102. var self = this,
  103. plugin = CKEDITOR.plugins.scayt,
  104. graytGroups = ['grayt_description', 'grayt_suggest', 'grayt_control'],
  105. menuGroup = 'scaytButton';
  106. editor.addMenuGroup(menuGroup);
  107. var items_order = editor.config.scayt_contextMenuItemsOrder.split('|');
  108. for(var pos = 0 ; pos < items_order.length ; pos++) {
  109. items_order[pos] = 'scayt_' + items_order[pos];
  110. }
  111. items_order = graytGroups.concat(items_order);
  112. if(items_order && items_order.length) {
  113. for(var pos = 0 ; pos < items_order.length ; pos++) {
  114. editor.addMenuGroup(items_order[pos], pos - 10);
  115. }
  116. }
  117. editor.addCommand( 'scaytToggle', {
  118. exec: function(editor) {
  119. var scaytInstance = editor.scayt;
  120. plugin.state.scayt[editor.name] = !plugin.state.scayt[editor.name];
  121. if(plugin.state.scayt[editor.name] === true) {
  122. if(!scaytInstance) {
  123. plugin.createScayt(editor);
  124. }
  125. } else {
  126. if(scaytInstance) {
  127. plugin.destroy(editor);
  128. }
  129. }
  130. }
  131. } );
  132. editor.addCommand( 'scaytAbout', {
  133. exec: function(editor) {
  134. var scaytInstance = editor.scayt;
  135. scaytInstance.tabToOpen = 'about';
  136. editor.lockSelection();
  137. editor.openDialog(self.dialogName);
  138. }
  139. } );
  140. editor.addCommand( 'scaytOptions', {
  141. exec: function(editor) {
  142. var scaytInstance = editor.scayt;
  143. scaytInstance.tabToOpen = 'options';
  144. editor.lockSelection();
  145. editor.openDialog(self.dialogName);
  146. }
  147. } );
  148. editor.addCommand( 'scaytLangs', {
  149. exec: function(editor) {
  150. var scaytInstance = editor.scayt;
  151. scaytInstance.tabToOpen = 'langs';
  152. editor.lockSelection();
  153. editor.openDialog(self.dialogName);
  154. }
  155. } );
  156. editor.addCommand( 'scaytDict', {
  157. exec: function(editor) {
  158. var scaytInstance = editor.scayt;
  159. scaytInstance.tabToOpen = 'dictionaries';
  160. editor.lockSelection();
  161. editor.openDialog(self.dialogName);
  162. }
  163. } );
  164. var uiMenuItems = {
  165. scaytToggle: {
  166. label : editor.lang.scayt.btn_enable,
  167. group : menuGroup,
  168. command: 'scaytToggle'
  169. },
  170. scaytAbout: {
  171. label : editor.lang.scayt.btn_about,
  172. group : menuGroup,
  173. command: 'scaytAbout'
  174. },
  175. scaytOptions: {
  176. label : editor.lang.scayt.btn_options,
  177. group : menuGroup,
  178. command: 'scaytOptions'
  179. },
  180. scaytLangs: {
  181. label : editor.lang.scayt.btn_langs,
  182. group : menuGroup,
  183. command: 'scaytLangs'
  184. },
  185. scaytDict: {
  186. label : editor.lang.scayt.btn_dictionaries,
  187. group : menuGroup,
  188. command: 'scaytDict'
  189. }
  190. };
  191. if(editor.plugins.wsc) {
  192. uiMenuItems.WSC = {
  193. label : editor.lang.wsc.toolbar,
  194. group : menuGroup,
  195. onClick: function() {
  196. var inlineMode = (editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE),
  197. plugin = CKEDITOR.plugins.scayt,
  198. scaytInstance = editor.scayt,
  199. text = inlineMode ? editor.container.getText() : editor.document.getBody().getText();
  200. text = text.replace(/\s/g, '');
  201. if(text) {
  202. if(scaytInstance && plugin.state.scayt[editor.name] && scaytInstance.setMarkupPaused) {
  203. scaytInstance.setMarkupPaused(true);
  204. }
  205. editor.lockSelection();
  206. editor.execCommand('checkspell');
  207. } else {
  208. alert('Nothing to check!');
  209. }
  210. }
  211. }
  212. }
  213. editor.addMenuItems(uiMenuItems);
  214. },
  215. bindEvents: function(editor) {
  216. var self = this,
  217. plugin = CKEDITOR.plugins.scayt,
  218. inline_mode = (editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE);
  219. var scaytDestroy = function() {
  220. plugin.destroy(editor);
  221. };
  222. /*
  223. * Dirty fix for placeholder drag&drop
  224. * Should be fixed with next release
  225. */
  226. /*
  227. editor.on('drop', function(evt) {
  228. var dropRange = evt.data.dropRange;
  229. var b = dropRange.createBookmark(true);
  230. editor.scayt.removeMarkupInSelectionNode({ selectionNode: evt.data.target.$, forceBookmark: false });
  231. dropRange.moveToBookmark(b);
  232. evt.data.dropRange = dropRange;
  233. return evt;
  234. }, this, null, 0); // We should be sure that we modify dropRange before CKEDITOR.plugins.clipboard calls
  235. */
  236. var contentDomReady = function() {
  237. // The event is fired when editable iframe node was reinited so we should restart our service
  238. if (plugin.state.scayt[editor.name] && !editor.readOnly && !editor.scayt) {
  239. plugin.createScayt(editor);
  240. }
  241. };
  242. var addMarkupStateHandlers = function() {
  243. var editable = editor.editable();
  244. editable.attachListener( editable, 'focus', function( evt ) {
  245. if( CKEDITOR.plugins.scayt && !editor.scayt ) {
  246. setTimeout(contentDomReady, 0); // we need small timeout in order to correctly set initial 'focused' option value in SCAYT core
  247. }
  248. var pluginStatus = CKEDITOR.plugins.scayt && CKEDITOR.plugins.scayt.state.scayt[editor.name] && editor.scayt,
  249. selectedElement, ranges, textLength, range;
  250. if((inline_mode ? true : pluginStatus) && editor._.savedSelection) {
  251. selectedElement = editor._.savedSelection.getSelectedElement();
  252. ranges = !selectedElement && editor._.savedSelection.getRanges();
  253. for(var i = 0; i < ranges.length; i++) {
  254. range = ranges[i];
  255. // we need to check type of node value in order to avoid error in IE when accessing 'nodeValue' property
  256. if(typeof range.startContainer.$.nodeValue === 'string') {
  257. textLength = range.startContainer.getText().length;
  258. if(textLength < range.startOffset || textLength < range.endOffset) {
  259. editor.unlockSelection(false);
  260. }
  261. }
  262. }
  263. }
  264. }, this, null, -10 ); // priority "-10" is set to call SCAYT CKEDITOR.editor#unlockSelection before CKEDITOR.editor#unlockSelection call
  265. };
  266. var contentDomHandler = function() {
  267. if(inline_mode) {
  268. if (!editor.config.scayt_inlineModeImmediateMarkup) {
  269. /*
  270. * Give an opportunity to CKEditor to perform all needed updates
  271. * and only after that call 'scaytDestroy' method (#72725)
  272. */
  273. editor.on('blur', function () { setTimeout( scaytDestroy, 0 ); } );
  274. editor.on('focus', contentDomReady);
  275. // We need to check if editor has focus(created) right now.
  276. // If editor is active - make attempt to create scayt
  277. if(editor.focusManager.hasFocus) {
  278. contentDomReady();
  279. }
  280. } else {
  281. contentDomReady();
  282. }
  283. } else {
  284. contentDomReady();
  285. }
  286. addMarkupStateHandlers();
  287. /*
  288. * 'mousedown' handler handle widget selection (click on widget). To
  289. * fix the issue when widget#wrapper referenced to element which can
  290. * be broken after markup.
  291. */
  292. var editable = editor.editable();
  293. editable.attachListener(editable, 'mousedown', function( evt ) {
  294. var target = evt.data.getTarget();
  295. var widget = editor.widgets && editor.widgets.getByElement( target );
  296. if ( widget ) {
  297. widget.wrapper = target.getAscendant( function( el ) {
  298. return el.hasAttribute( 'data-cke-widget-wrapper' )
  299. }, true );
  300. }
  301. }, this, null, -10); // '-10': we need to be shure that widget#wrapper updated before any other calls
  302. };
  303. editor.on('contentDom', contentDomHandler);
  304. editor.on('beforeCommandExec', function(ev) {
  305. var scaytInstance = editor.scayt,
  306. selectedLangElement = null,
  307. forceBookmark = false,
  308. removeMarkupInsideSelection = true;
  309. // TODO: after switching in source mode not recreate SCAYT instance, try to just rerun markuping to don't make requests to server
  310. if(ev.data.name in plugin.options.disablingCommandExec && editor.mode == 'wysiwyg') {
  311. if(scaytInstance) {
  312. plugin.destroy(editor);
  313. editor.fire('scaytButtonState', CKEDITOR.TRISTATE_DISABLED);
  314. }
  315. } else if( ev.data.name === 'bold' || ev.data.name === 'italic' || ev.data.name === 'underline' ||
  316. ev.data.name === 'strike' || ev.data.name === 'subscript' || ev.data.name === 'superscript' ||
  317. ev.data.name === 'enter' || ev.data.name === 'cut' || ev.data.name === 'language') {
  318. if(scaytInstance) {
  319. if(ev.data.name === 'cut') {
  320. removeMarkupInsideSelection = false;
  321. // We need to force bookmark before we remove our markup.
  322. // Otherwise we will get issues with cutting text via context menu.
  323. forceBookmark = true;
  324. }
  325. // We need to remove all SCAYT markup from 'lang' node before it will be deleted.
  326. // We need to remove SCAYT markup from selected text before creating 'lang' node as well.
  327. if(ev.data.name === 'language') {
  328. selectedLangElement = editor.plugins.language.getCurrentLangElement(editor);
  329. selectedLangElement = selectedLangElement && selectedLangElement.$;
  330. // We need to force bookmark before we remove our markup.
  331. // Otherwise we will get issues with cutting text via language plugin menu.
  332. forceBookmark = true;
  333. }
  334. editor.fire('reloadMarkupScayt', {
  335. removeOptions: {
  336. removeInside: removeMarkupInsideSelection,
  337. forceBookmark: forceBookmark,
  338. selectionNode: selectedLangElement
  339. },
  340. timeout: 0
  341. });
  342. }
  343. }
  344. });
  345. editor.on('beforeSetMode', function(ev) {
  346. var scaytInstance;
  347. // needed when we use:
  348. // CKEDITOR.instances.editor_ID.setMode("source")
  349. // CKEDITOR.instances.editor_ID.setMode("wysiwyg")
  350. // can't be implemented in editor.on('mode', function(ev) {});
  351. if (ev.data == 'source') {
  352. scaytInstance = editor.scayt;
  353. if(scaytInstance) {
  354. plugin.destroy(editor);
  355. editor.fire('scaytButtonState', CKEDITOR.TRISTATE_DISABLED);
  356. }
  357. // remove custom data from body, to prevent waste properties showing in IE8
  358. if(editor.document) { //GitHub #84 : make sure that document exists(e.g. when startup mode set to 'source')
  359. editor.document.getBody().removeAttribute('_jquid');
  360. }
  361. }
  362. });
  363. editor.on('afterCommandExec', function(ev) {
  364. if(editor.mode == 'wysiwyg' && (ev.data.name == 'undo' || ev.data.name == 'redo')) {
  365. setTimeout(function() {
  366. var scaytInstance = editor.scayt,
  367. scaytLangList = scaytInstance && scaytInstance.getScaytLangList();
  368. /*
  369. * Checks SCAYT initialization of LangList. To prevent immediate
  370. * markup which is triggered by 'startSpellCheck' event.
  371. * E.g.: Drop into inline CKEDITOR with scayt_autoStartup = true;
  372. */
  373. if (!scaytLangList || !(scaytLangList.ltr && scaytLangList.rtl)) return;
  374. scaytInstance.fire('startSpellCheck, startGrammarCheck');
  375. }, 250);
  376. }
  377. });
  378. // handle readonly changes
  379. editor.on('readOnly', function(ev) {
  380. var scaytInstance;
  381. if(ev) {
  382. scaytInstance = editor.scayt;
  383. if(ev.editor.readOnly === true) {
  384. if(scaytInstance) {
  385. scaytInstance.fire('removeMarkupInDocument', {});
  386. }
  387. } else {
  388. if(scaytInstance) {
  389. scaytInstance.fire('startSpellCheck, startGrammarCheck');
  390. } else if(ev.editor.mode == 'wysiwyg' && plugin.state.scayt[ev.editor.name] === true) {
  391. plugin.createScayt(editor);
  392. ev.editor.fire('scaytButtonState', CKEDITOR.TRISTATE_ON);
  393. }
  394. }
  395. }
  396. });
  397. // we need to destroy SCAYT before CK editor will be completely destroyed
  398. editor.on('beforeDestroy', scaytDestroy);
  399. //#9439 after SetData method fires contentDom event and SCAYT create additional instanse
  400. // This way we should destroy SCAYT on setData event when contenteditable Iframe was re-created
  401. editor.on('setData', function() {
  402. scaytDestroy();
  403. // in inline mode SetData does not fire contentDom event
  404. if(editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE || editor.plugins.divarea) {
  405. contentDomHandler();
  406. }
  407. }, this, null, 50);
  408. /*
  409. * Main entry point to react on changes in document
  410. */
  411. editor.on('reloadMarkupScayt', function(ev) {
  412. var removeOptions = ev.data && ev.data.removeOptions,
  413. timeout = ev.data && ev.data.timeout;
  414. /*
  415. * Perform removeMarkupInSelectionNode and 'startSpellCheck' fire
  416. * asynchroniosly and keep CKEDITOR flow as expected
  417. */
  418. setTimeout(function() {
  419. var scaytInstance = editor.scayt,
  420. scaytLangList = scaytInstance && scaytInstance.getScaytLangList();
  421. /*
  422. * Checks SCAYT initialization of LangList. To prevent immediate
  423. * markup which is triggered by 'startSpellCheck' event.
  424. * E.g.: Drop into inline CKEDITOR with scayt_autoStartup = true;
  425. */
  426. if (!scaytLangList || !(scaytLangList.ltr && scaytLangList.rtl)) return;
  427. /*
  428. * CKEditor can keep \u200B character in document (with selection#selectRanges)
  429. * we need to take care about that. For this case we fire
  430. * 'keydown' [left arrow], what will trigger 'removeFillingChar' on Webkit
  431. * to cleanup the document
  432. */
  433. editor.document.fire( 'keydown', new CKEDITOR.dom.event( { keyCode: 37 } ) );
  434. /* trigger remove markup with 'startSpellCheck' */
  435. scaytInstance.removeMarkupInSelectionNode(removeOptions);
  436. scaytInstance.fire('startSpellCheck, startGrammarCheck');
  437. }, timeout || 0 );
  438. });
  439. // Reload spell-checking for current word after insertion completed.
  440. editor.on('insertElement', function() {
  441. // IE bug: we need wait here to make sure that focus is returned to editor, and we can store the selection before we proceed with markup
  442. editor.fire('reloadMarkupScayt', {removeOptions: {forceBookmark: true}});
  443. }, this, null, 50);
  444. editor.on('insertHtml', function() {
  445. editor.fire('reloadMarkupScayt');
  446. }, this, null, 50);
  447. editor.on('insertText', function() {
  448. editor.fire('reloadMarkupScayt');
  449. }, this, null, 50);
  450. // The event is listening to open necessary dialog tab
  451. editor.on('scaytDialogShown', function(ev) {
  452. var dialog = ev.data,
  453. scaytInstance = editor.scayt;
  454. dialog.selectPage(scaytInstance.tabToOpen);
  455. });
  456. },
  457. parseConfig: function(editor) {
  458. var plugin = CKEDITOR.plugins.scayt;
  459. // preprocess config for backward compatibility
  460. plugin.replaceOldOptionsNames(editor.config);
  461. // Checking editor's config after initialization
  462. if(typeof editor.config.scayt_autoStartup !== 'boolean') {
  463. editor.config.scayt_autoStartup = false;
  464. }
  465. plugin.state.scayt[editor.name] = editor.config.scayt_autoStartup;
  466. if(typeof editor.config.grayt_autoStartup !== 'boolean') {
  467. editor.config.grayt_autoStartup = false;
  468. }
  469. if(typeof editor.config.scayt_inlineModeImmediateMarkup !== 'boolean') {
  470. editor.config.scayt_inlineModeImmediateMarkup = false;
  471. }
  472. plugin.state.grayt[editor.name] = editor.config.grayt_autoStartup;
  473. if(!editor.config.scayt_contextCommands) {
  474. editor.config.scayt_contextCommands = 'ignore|ignoreall|add';
  475. }
  476. if(!editor.config.scayt_contextMenuItemsOrder) {
  477. editor.config.scayt_contextMenuItemsOrder = 'suggest|moresuggest|control';
  478. }
  479. if(!editor.config.scayt_sLang) {
  480. editor.config.scayt_sLang = 'en_US';
  481. }
  482. if(editor.config.scayt_maxSuggestions === undefined || typeof editor.config.scayt_maxSuggestions != 'number' || editor.config.scayt_maxSuggestions < 0) {
  483. editor.config.scayt_maxSuggestions = 5;
  484. }
  485. if(editor.config.scayt_minWordLength === undefined || typeof editor.config.scayt_minWordLength != 'number' || editor.config.scayt_minWordLength < 1) {
  486. editor.config.scayt_minWordLength = 4;
  487. }
  488. if(editor.config.scayt_customDictionaryIds === undefined || typeof editor.config.scayt_customDictionaryIds !== 'string') {
  489. editor.config.scayt_customDictionaryIds = '';
  490. }
  491. if(editor.config.scayt_userDictionaryName === undefined || typeof editor.config.scayt_userDictionaryName !== 'string') {
  492. editor.config.scayt_userDictionaryName = null;
  493. }
  494. if(typeof editor.config.scayt_uiTabs === 'string' && editor.config.scayt_uiTabs.split(',').length === 3) {
  495. var scayt_uiTabs = [], _tempUITabs = [];
  496. editor.config.scayt_uiTabs = editor.config.scayt_uiTabs.split(',');
  497. CKEDITOR.tools.search(editor.config.scayt_uiTabs, function(value) {
  498. if (Number(value) === 1 || Number(value) === 0) {
  499. _tempUITabs.push(true);
  500. scayt_uiTabs.push(Number(value));
  501. } else {
  502. _tempUITabs.push(false);
  503. }
  504. });
  505. if (CKEDITOR.tools.search(_tempUITabs, false) === null) {
  506. editor.config.scayt_uiTabs = scayt_uiTabs;
  507. } else {
  508. editor.config.scayt_uiTabs = [1,1,1];
  509. }
  510. } else {
  511. editor.config.scayt_uiTabs = [1,1,1];
  512. }
  513. if(typeof editor.config.scayt_serviceProtocol != 'string') {
  514. editor.config.scayt_serviceProtocol = null;
  515. }
  516. if(typeof editor.config.scayt_serviceHost != 'string') {
  517. editor.config.scayt_serviceHost = null;
  518. }
  519. if(typeof editor.config.scayt_servicePort != 'string') {
  520. editor.config.scayt_servicePort = null;
  521. }
  522. if(typeof editor.config.scayt_servicePath != 'string') {
  523. editor.config.scayt_servicePath = null;
  524. }
  525. if(!editor.config.scayt_moreSuggestions) {
  526. editor.config.scayt_moreSuggestions = 'on';
  527. }
  528. if(typeof editor.config.scayt_customerId !== 'string') {
  529. editor.config.scayt_customerId = '1:WvF0D4-UtPqN1-43nkD4-NKvUm2-daQqk3-LmNiI-z7Ysb4-mwry24-T8YrS3-Q2tpq2';
  530. }
  531. if(typeof editor.config.scayt_srcUrl !== 'string') {
  532. var protocol = document.location.protocol;
  533. protocol = protocol.search(/https?:/) != -1 ? protocol : 'http:';
  534. editor.config.scayt_srcUrl = protocol + '//svc.webspellchecker.net/spellcheck31/lf/scayt3/ckscayt/ckscayt.js';
  535. }
  536. if(typeof CKEDITOR.config.scayt_handleCheckDirty !== 'boolean') {
  537. CKEDITOR.config.scayt_handleCheckDirty = true;
  538. }
  539. if(typeof CKEDITOR.config.scayt_handleUndoRedo !== 'boolean') {
  540. /* set default as 'true' */
  541. CKEDITOR.config.scayt_handleUndoRedo = true;
  542. }
  543. /* checking 'undo' plugin, if no disable SCAYT handler */
  544. CKEDITOR.config.scayt_handleUndoRedo = CKEDITOR.plugins.undo ? CKEDITOR.config.scayt_handleUndoRedo : false;
  545. if(typeof editor.config.scayt_multiLanguageMode !== 'boolean') {
  546. editor.config.scayt_multiLanguageMode = false;
  547. }
  548. if(typeof editor.config.scayt_multiLanguageStyles !== 'object') {
  549. editor.config.scayt_multiLanguageStyles = {};
  550. }
  551. if(editor.config.scayt_ignoreAllCapsWords && typeof editor.config.scayt_ignoreAllCapsWords !== 'boolean') {
  552. editor.config.scayt_ignoreAllCapsWords = false;
  553. }
  554. if(editor.config.scayt_ignoreDomainNames && typeof editor.config.scayt_ignoreDomainNames !== 'boolean') {
  555. editor.config.scayt_ignoreDomainNames = false;
  556. }
  557. if(editor.config.scayt_ignoreWordsWithMixedCases && typeof editor.config.scayt_ignoreWordsWithMixedCases !== 'boolean') {
  558. editor.config.scayt_ignoreWordsWithMixedCases = false;
  559. }
  560. if(editor.config.scayt_ignoreWordsWithNumbers && typeof editor.config.scayt_ignoreWordsWithNumbers !== 'boolean') {
  561. editor.config.scayt_ignoreWordsWithNumbers = false;
  562. }
  563. if( editor.config.scayt_disableOptionsStorage ) {
  564. var userOptions = CKEDITOR.tools.isArray( editor.config.scayt_disableOptionsStorage ) ? editor.config.scayt_disableOptionsStorage : ( typeof editor.config.scayt_disableOptionsStorage === 'string' ) ? [ editor.config.scayt_disableOptionsStorage ] : undefined,
  565. availableValue = [ 'all', 'options', 'lang', 'ignore-all-caps-words', 'ignore-domain-names', 'ignore-words-with-mixed-cases', 'ignore-words-with-numbers'],
  566. valuesOption = ['lang', 'ignore-all-caps-words', 'ignore-domain-names', 'ignore-words-with-mixed-cases', 'ignore-words-with-numbers'],
  567. search = CKEDITOR.tools.search,
  568. indexOf = CKEDITOR.tools.indexOf;
  569. var isValidOption = function( option ) {
  570. return !!search( availableValue, option );
  571. };
  572. var makeOptionsToStorage = function( options ) {
  573. var retval = [];
  574. for (var i = 0; i < options.length; i++) {
  575. var value = options[i],
  576. isGroupOptionInUserOptions = !!search( options, 'options' );
  577. if( !isValidOption( value ) || isGroupOptionInUserOptions && !!search( valuesOption, function( val ) { if( val === 'lang' ) { return false; } } ) ) {
  578. return;
  579. }
  580. if( !!search( valuesOption, value ) ) {
  581. valuesOption.splice( indexOf( valuesOption, value ), 1 );
  582. }
  583. if( value === 'all' || isGroupOptionInUserOptions && !!search( options, 'lang' )) {
  584. return [];
  585. }
  586. if( value === 'options' ) {
  587. valuesOption = [ 'lang' ];
  588. }
  589. }
  590. retval = retval.concat( valuesOption );
  591. return retval;
  592. };
  593. editor.config.scayt_disableOptionsStorage = makeOptionsToStorage( userOptions );
  594. }
  595. },
  596. addRule: function(editor) {
  597. var plugin = CKEDITOR.plugins.scayt,
  598. dataProcessor = editor.dataProcessor,
  599. htmlFilter = dataProcessor && dataProcessor.htmlFilter,
  600. pathFilters = editor._.elementsPath && editor._.elementsPath.filters,
  601. dataFilter = dataProcessor && dataProcessor.dataFilter,
  602. removeFormatFilter = editor.addRemoveFormatFilter,
  603. pathFilter = function(element) {
  604. var scaytInstance = editor.scayt;
  605. if( scaytInstance && (element.hasAttribute(plugin.options.data_attribute_name) || element.hasAttribute(plugin.options.problem_grammar_data_attribute)) ) {
  606. return false;
  607. }
  608. },
  609. removeFormatFilterTemplate = function(element) {
  610. var scaytInstance = editor.scayt,
  611. result = true;
  612. if( scaytInstance && (element.hasAttribute(plugin.options.data_attribute_name) || element.hasAttribute(plugin.options.problem_grammar_data_attribute)) ) {
  613. result = false;
  614. }
  615. return result;
  616. };
  617. if(pathFilters) {
  618. pathFilters.push(pathFilter);
  619. }
  620. if(dataFilter) {
  621. var dataFilterRules = {
  622. elements: {
  623. span: function(element) {
  624. var scaytState = element.hasClass(plugin.options.misspelled_word_class) && element.attributes[plugin.options.data_attribute_name],
  625. graytState = element.hasClass(plugin.options.problem_grammar_class) && element.attributes[plugin.options.problem_grammar_data_attribute];
  626. if(plugin && (scaytState || graytState)) {
  627. delete element.name;
  628. }
  629. return element;
  630. }
  631. }
  632. };
  633. dataFilter.addRules(dataFilterRules);
  634. }
  635. if (htmlFilter) {
  636. var htmlFilterRules = {
  637. elements: {
  638. span: function(element) {
  639. var scaytState = element.hasClass(plugin.options.misspelled_word_class) && element.attributes[plugin.options.data_attribute_name],
  640. graytState = element.hasClass(plugin.options.problem_grammar_class) && element.attributes[plugin.options.problem_grammar_data_attribute];
  641. if(plugin && (scaytState || graytState)) {
  642. delete element.name;
  643. }
  644. return element;
  645. }
  646. }
  647. };
  648. htmlFilter.addRules(htmlFilterRules);
  649. }
  650. if(removeFormatFilter) {
  651. removeFormatFilter.call(editor, removeFormatFilterTemplate);
  652. }
  653. },
  654. scaytMenuDefinition: function(editor) {
  655. var self = this,
  656. plugin = CKEDITOR.plugins.scayt,
  657. scayt_instance = editor.scayt;
  658. return {
  659. scayt: {
  660. scayt_ignore: {
  661. label: scayt_instance.getLocal('btn_ignore'),
  662. group : 'scayt_control',
  663. order : 1,
  664. exec: function(editor) {
  665. var scaytInstance = editor.scayt;
  666. scaytInstance.ignoreWord();
  667. }
  668. },
  669. scayt_ignoreall: {
  670. label : scayt_instance.getLocal('btn_ignoreAll'),
  671. group : 'scayt_control',
  672. order : 2,
  673. exec: function(editor) {
  674. var scaytInstance = editor.scayt;
  675. scaytInstance.ignoreAllWords();
  676. }
  677. },
  678. scayt_add: {
  679. label : scayt_instance.getLocal('btn_addWord'),
  680. group : 'scayt_control',
  681. order : 3,
  682. exec : function(editor) {
  683. var scaytInstance = editor.scayt;
  684. // @TODO: We need to add set/restore bookmark logic to 'addWordToUserDictionary' method inside dictionarymanager.
  685. // Timeout is used as tmp fix for IE9, when after hitting 'Add word' menu item, document container was blurred.
  686. setTimeout(function() {
  687. scaytInstance.addWordToUserDictionary();
  688. }, 10);
  689. }
  690. },
  691. scayt_option: {
  692. label : scayt_instance.getLocal('btn_options'),
  693. group : 'scayt_control',
  694. order : 4,
  695. exec: function(editor) {
  696. var scaytInstance = editor.scayt;
  697. scaytInstance.tabToOpen = 'options';
  698. editor.lockSelection();
  699. editor.openDialog(self.dialogName);
  700. },
  701. verification: function(editor) {
  702. return (editor.config.scayt_uiTabs[0] == 1) ? true : false;
  703. }
  704. },
  705. scayt_language: {
  706. label : scayt_instance.getLocal('btn_langs'),
  707. group : 'scayt_control',
  708. order : 5,
  709. exec: function(editor) {
  710. var scaytInstance = editor.scayt;
  711. scaytInstance.tabToOpen = 'langs';
  712. editor.lockSelection();
  713. editor.openDialog(self.dialogName);
  714. },
  715. verification: function(editor) {
  716. return (editor.config.scayt_uiTabs[1] == 1) ? true : false;
  717. }
  718. },
  719. scayt_dictionary: {
  720. label : scayt_instance.getLocal('btn_dictionaries'),
  721. group : 'scayt_control',
  722. order : 6,
  723. exec: function(editor) {
  724. var scaytInstance = editor.scayt;
  725. scaytInstance.tabToOpen = 'dictionaries';
  726. editor.lockSelection();
  727. editor.openDialog(self.dialogName);
  728. },
  729. verification: function(editor) {
  730. return (editor.config.scayt_uiTabs[2] == 1) ? true : false;
  731. }
  732. },
  733. scayt_about: {
  734. label : scayt_instance.getLocal('btn_about'),
  735. group : 'scayt_control',
  736. order : 7,
  737. exec: function(editor) {
  738. var scaytInstance = editor.scayt;
  739. scaytInstance.tabToOpen = 'about';
  740. editor.lockSelection();
  741. editor.openDialog(self.dialogName);
  742. }
  743. }
  744. },
  745. grayt: {
  746. grayt_problemdescription: {
  747. label : 'Grammar problem description',
  748. group : 'grayt_description', // look at addMenuItems method for further info
  749. order : 1,
  750. state : CKEDITOR.TRISTATE_DISABLED,
  751. exec: function(editor) {}
  752. },
  753. grayt_ignore: {
  754. label : scayt_instance.getLocal('btn_ignore'),
  755. group : 'grayt_control',
  756. order : 2,
  757. exec: function(editor) {
  758. var scaytInstance = editor.scayt;
  759. scaytInstance.ignorePhrase();
  760. }
  761. }
  762. }
  763. };
  764. },
  765. buildSuggestionMenuItems: function(editor, suggestions, isScaytNode) {
  766. var self = this,
  767. itemList = {},
  768. subItemList = {},
  769. replaceKeyName = isScaytNode ? 'word' : 'phrase',
  770. updateEventName = isScaytNode ? 'startGrammarCheck' : 'startSpellCheck',
  771. plugin = CKEDITOR.plugins.scayt,
  772. scayt_instance = editor.scayt;
  773. if(suggestions.length > 0 && suggestions[0] !== 'no_any_suggestions') {
  774. if(isScaytNode) {
  775. // build SCAYT suggestions
  776. for(var i = 0; i < suggestions.length; i++) {
  777. var commandName = 'scayt_suggest_' + CKEDITOR.plugins.scayt.suggestions[i].replace(' ', '_');
  778. editor.addCommand(commandName, self.createCommand(CKEDITOR.plugins.scayt.suggestions[i], replaceKeyName, updateEventName));
  779. if(i < editor.config.scayt_maxSuggestions) {
  780. // mainSuggestions
  781. editor.addMenuItem(commandName, {
  782. label: suggestions[i],
  783. command: commandName,
  784. group: 'scayt_suggest',
  785. order: i + 1
  786. });
  787. itemList[commandName] = CKEDITOR.TRISTATE_OFF;
  788. } else {
  789. // moreSuggestions
  790. editor.addMenuItem(commandName, {
  791. label: suggestions[i],
  792. command: commandName,
  793. group: 'scayt_moresuggest',
  794. order: i + 1
  795. });
  796. subItemList[commandName] = CKEDITOR.TRISTATE_OFF;
  797. if(editor.config.scayt_moreSuggestions === 'on') {
  798. editor.addMenuItem('scayt_moresuggest', {
  799. label : scayt_instance.getLocal('btn_moreSuggestions'),
  800. group : 'scayt_moresuggest',
  801. order : 10,
  802. getItems : function() {
  803. return subItemList;
  804. }
  805. });
  806. itemList['scayt_moresuggest'] = CKEDITOR.TRISTATE_OFF;
  807. }
  808. }
  809. }
  810. } else {
  811. // build GRAYT suggestions
  812. for(var i = 0; i < suggestions.length; i++) {
  813. var commandName = 'grayt_suggest_' + CKEDITOR.plugins.scayt.suggestions[i].replace(' ', '_');
  814. editor.addCommand(commandName, self.createCommand(CKEDITOR.plugins.scayt.suggestions[i], replaceKeyName, updateEventName));
  815. // mainSuggestions
  816. editor.addMenuItem(commandName, {
  817. label: suggestions[i],
  818. command: commandName,
  819. group: 'grayt_suggest',
  820. order: i + 1
  821. });
  822. itemList[commandName] = CKEDITOR.TRISTATE_OFF;
  823. }
  824. }
  825. } else {
  826. var noSuggestionsCommand = 'no_scayt_suggest';
  827. itemList[noSuggestionsCommand] = CKEDITOR.TRISTATE_DISABLED;
  828. editor.addCommand(noSuggestionsCommand, {
  829. exec: function() {
  830. }
  831. });
  832. editor.addMenuItem(noSuggestionsCommand, {
  833. label : scayt_instance.getLocal('btn_noSuggestions') || noSuggestionsCommand,
  834. command: noSuggestionsCommand,
  835. group : 'scayt_suggest',
  836. order : 0
  837. });
  838. }
  839. return itemList;
  840. },
  841. menuGenerator: function(editor, selectionNode) {
  842. var self = this,
  843. scaytInstance = editor.scayt,
  844. menuItems = this.scaytMenuDefinition(editor),
  845. itemList = {},
  846. allowedOption = editor.config.scayt_contextCommands.split('|'),
  847. lang = selectionNode.getAttribute(scaytInstance.getLangAttribute()) || scaytInstance.getLang(),
  848. word, grammarPhrase, isScaytNode, isGrammarNode, problemDescriptionText;
  849. isScaytNode = scaytInstance.isScaytNode(selectionNode);
  850. isGrammarNode = scaytInstance.isGraytNode(selectionNode);
  851. if(isScaytNode) {
  852. // we clicked scayt misspelling
  853. // get suggestions
  854. menuItems = menuItems.scayt;
  855. word = selectionNode.getAttribute(scaytInstance.getScaytNodeAttributeName());
  856. scaytInstance.fire('getSuggestionsList', {
  857. lang: lang,
  858. word: word
  859. });
  860. itemList = this.buildSuggestionMenuItems(editor, CKEDITOR.plugins.scayt.suggestions, isScaytNode);
  861. } else if(isGrammarNode) {
  862. // we clicked grammar problem
  863. // get suggestions
  864. menuItems = menuItems.grayt;
  865. grammarPhrase = selectionNode.getAttribute(scaytInstance.getGraytNodeAttributeName());
  866. // setup grammar problem description
  867. problemDescriptionText = scaytInstance.getProblemDescriptionText(grammarPhrase, lang);
  868. if(menuItems.grayt_problemdescription && problemDescriptionText) {
  869. menuItems.grayt_problemdescription.label = problemDescriptionText;
  870. }
  871. scaytInstance.fire('getGrammarSuggestionsList', {
  872. lang: lang,
  873. phrase: grammarPhrase
  874. });
  875. itemList = this.buildSuggestionMenuItems(editor, CKEDITOR.plugins.scayt.suggestions, isScaytNode);
  876. }
  877. if(isScaytNode && editor.config.scayt_contextCommands == 'off') {
  878. return itemList;
  879. }
  880. for(var key in menuItems) {
  881. if(isScaytNode && CKEDITOR.tools.indexOf(allowedOption, key.replace('scayt_', '')) == -1 && editor.config.scayt_contextCommands != 'all') {
  882. continue;
  883. }
  884. if(typeof menuItems[key].state != 'undefined') {
  885. itemList[key] = menuItems[key].state;
  886. } else {
  887. itemList[key] = CKEDITOR.TRISTATE_OFF;
  888. }
  889. // delete item from context menu if its state isn't verified as allowed
  890. if(typeof menuItems[key].verification === 'function' && !menuItems[key].verification(editor)) {
  891. // itemList[key] = (menuItems[key].verification(editor)) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
  892. delete itemList[key];
  893. }
  894. editor.addCommand(key, {
  895. exec: menuItems[key].exec
  896. });
  897. editor.addMenuItem(key, {
  898. label : editor.lang.scayt[menuItems[key].label] || menuItems[key].label,
  899. command: key,
  900. group : menuItems[key].group,
  901. order : menuItems[key].order
  902. });
  903. }
  904. return itemList;
  905. },
  906. createCommand: function(suggestion, replaceKeyName, updateEventName) {
  907. return {
  908. exec: function(editor) {
  909. var scaytInstance = editor.scayt,
  910. eventObject = {};
  911. eventObject[replaceKeyName] = suggestion;
  912. scaytInstance.replaceSelectionNode(eventObject);
  913. // we need to remove grammar markup from selection node if we just performed replace action for misspelling
  914. if(updateEventName === 'startGrammarCheck') {
  915. scaytInstance.removeMarkupInSelectionNode({grammarOnly: true});
  916. }
  917. // for grayt problem replacement we need to fire 'startSpellCheck'
  918. // for scayt misspelling replacement we need to fire 'startGrammarCheck'
  919. scaytInstance.fire(updateEventName);
  920. }
  921. };
  922. }
  923. });
  924. CKEDITOR.plugins.scayt = {
  925. state: {
  926. scayt: {},
  927. grayt: {}
  928. },
  929. suggestions: [],
  930. loadingHelper: {
  931. loadOrder: []
  932. },
  933. isLoading: false,
  934. options: {
  935. disablingCommandExec: {
  936. source: true,
  937. newpage: true,
  938. templates: true
  939. },
  940. data_attribute_name: 'data-scayt-word',
  941. misspelled_word_class: 'scayt-misspell-word',
  942. problem_grammar_data_attribute: 'data-grayt-phrase',
  943. problem_grammar_class: 'gramm-problem'
  944. },
  945. backCompatibilityMap: {
  946. 'scayt_service_protocol': 'scayt_serviceProtocol',
  947. 'scayt_service_host' : 'scayt_serviceHost',
  948. 'scayt_service_port' : 'scayt_servicePort',
  949. 'scayt_service_path' : 'scayt_servicePath',
  950. 'scayt_customerid' : 'scayt_customerId'
  951. },
  952. replaceOldOptionsNames: function(config) {
  953. for(var key in config) {
  954. if(key in this.backCompatibilityMap) {
  955. config[this.backCompatibilityMap[key]] = config[key];
  956. delete config[key];
  957. }
  958. }
  959. },
  960. createScayt : function(editor) {
  961. var self = this,
  962. plugin = CKEDITOR.plugins.scayt;
  963. this.loadScaytLibrary(editor, function(_editor) {
  964. var textContainer = _editor.window && _editor.window.getFrame() || _editor.editable();
  965. // Do not create SCAYT if there is no text container for usage
  966. if(!textContainer) {
  967. plugin.state.scayt[_editor.name] = false;
  968. return;
  969. }
  970. var scaytInstanceOptions = {
  971. lang : _editor.config.scayt_sLang,
  972. container : textContainer.$,
  973. customDictionary : _editor.config.scayt_customDictionaryIds,
  974. userDictionaryName : _editor.config.scayt_userDictionaryName,
  975. localization : _editor.langCode,
  976. customer_id : _editor.config.scayt_customerId,
  977. debug : _editor.config.scayt_debug,
  978. data_attribute_name : self.options.data_attribute_name,
  979. misspelled_word_class: self.options.misspelled_word_class,
  980. problem_grammar_data_attribute: self.options.problem_grammar_data_attribute,
  981. problem_grammar_class: self.options.problem_grammar_class,
  982. 'options-to-restore': _editor.config.scayt_disableOptionsStorage,
  983. focused : _editor.editable().hasFocus, // #30260 we need to set focused=true if CKEditor is focused before SCAYT initialization
  984. ignoreElementsRegex : _editor.config.scayt_elementsToIgnore,
  985. minWordLength : _editor.config.scayt_minWordLength,
  986. multiLanguageMode : _editor.config.scayt_multiLanguageMode,
  987. multiLanguageStyles : _editor.config.scayt_multiLanguageStyles,
  988. graytAutoStartup : plugin.state.grayt[_editor.name]
  989. };
  990. if(_editor.config.scayt_serviceProtocol) {
  991. scaytInstanceOptions['service_protocol'] = _editor.config.scayt_serviceProtocol;
  992. }
  993. if(_editor.config.scayt_serviceHost) {
  994. scaytInstanceOptions['service_host'] = _editor.config.scayt_serviceHost;
  995. }
  996. if(_editor.config.scayt_servicePort) {
  997. scaytInstanceOptions['service_port'] = _editor.config.scayt_servicePort;
  998. }
  999. if(_editor.config.scayt_servicePath) {
  1000. scaytInstanceOptions['service_path'] = _editor.config.scayt_servicePath;
  1001. }
  1002. //predefined options
  1003. if(typeof _editor.config.scayt_ignoreAllCapsWords === 'boolean') {
  1004. scaytInstanceOptions['ignore-all-caps-words'] = _editor.config.scayt_ignoreAllCapsWords;
  1005. }
  1006. if(typeof _editor.config.scayt_ignoreDomainNames === 'boolean') {
  1007. scaytInstanceOptions['ignore-domain-names'] = _editor.config.scayt_ignoreDomainNames;
  1008. }
  1009. if(typeof _editor.config.scayt_ignoreWordsWithMixedCases === 'boolean') {
  1010. scaytInstanceOptions['ignore-words-with-mixed-cases'] = _editor.config.scayt_ignoreWordsWithMixedCases;
  1011. }
  1012. if(typeof _editor.config.scayt_ignoreWordsWithNumbers === 'boolean') {
  1013. scaytInstanceOptions['ignore-words-with-numbers'] = _editor.config.scayt_ignoreWordsWithNumbers;
  1014. }
  1015. var scaytInstance = new SCAYT.CKSCAYT(scaytInstanceOptions, function() {
  1016. // success callback
  1017. }, function() {
  1018. // error callback
  1019. }),
  1020. wordsPrefix = 'word_';
  1021. scaytInstance.subscribe('suggestionListSend', function(data) {
  1022. // TODO: 1. Maybe store suggestions for specific editor
  1023. // TODO: 2. Fix issue with suggestion duplicates on on server
  1024. //CKEDITOR.plugins.scayt.suggestions = data.suggestionList;
  1025. var _wordsCollection = {},
  1026. _suggestionList =[];
  1027. for (var i = 0; i < data.suggestionList.length; i++) {
  1028. if (!_wordsCollection[wordsPrefix + data.suggestionList[i]]) {
  1029. _wordsCollection[wordsPrefix + data.suggestionList[i]] = data.suggestionList[i];
  1030. _suggestionList.push(data.suggestionList[i]);
  1031. }
  1032. }
  1033. CKEDITOR.plugins.scayt.suggestions = _suggestionList;
  1034. });
  1035. // if selection has changed programmatically by SCAYT we need to react appropriately
  1036. scaytInstance.subscribe('selectionIsChanged', function(data) {
  1037. var selection = _editor.getSelection();
  1038. if(selection.isLocked) {
  1039. _editor.lockSelection();
  1040. }
  1041. });
  1042. scaytInstance.subscribe('graytStateChanged', function(data) {
  1043. plugin.state.grayt[_editor.name] = data.state;
  1044. });
  1045. _editor.scayt = scaytInstance;
  1046. _editor.fire('scaytButtonState', _editor.readOnly ? CKEDITOR.TRISTATE_DISABLED : CKEDITOR.TRISTATE_ON);
  1047. });
  1048. },
  1049. destroy: function(editor) {
  1050. if(editor.scayt) {
  1051. editor.scayt.destroy();
  1052. }
  1053. delete editor.scayt;
  1054. editor.fire('scaytButtonState', CKEDITOR.TRISTATE_OFF);
  1055. },
  1056. loadScaytLibrary: function(editor, callback) {
  1057. var self = this,
  1058. date,
  1059. timestamp,
  1060. scaytUrl;
  1061. // no need to process load requests from same editor as it can cause bugs with
  1062. // loading ckscayt app due to subsequent calls of some events
  1063. // need to be before 'if' statement, because of timing issue in CKEDITOR.scriptLoader
  1064. // when callback executing is delayed for a few milliseconds, and scayt can be created twise
  1065. // on one instance
  1066. if(this.loadingHelper[editor.name]) return;
  1067. if(typeof window.SCAYT === 'undefined' || typeof window.SCAYT.CKSCAYT !== 'function') {
  1068. // add onLoad callbacks for editors while SCAYT is loading
  1069. this.loadingHelper[editor.name] = callback;
  1070. this.loadingHelper.loadOrder.push(editor.name);
  1071. //creating unique timestamp for SCAYT URL
  1072. date = new Date();
  1073. timestamp = date.getTime();
  1074. scaytUrl = editor.config.scayt_srcUrl;
  1075. //if there already implemented timstamp for scayr_srcURL use it, if not use our timestamp
  1076. scaytUrl = scaytUrl + (scaytUrl.indexOf('?') >= 0 ? '' : '?' + timestamp);
  1077. if (!this.loadingHelper.ckscaytLoading) {
  1078. CKEDITOR.scriptLoader.load(scaytUrl, function(success) {
  1079. var editorName;
  1080. if ( success ) {
  1081. CKEDITOR.fireOnce('scaytReady');
  1082. for(var i = 0; i < self.loadingHelper.loadOrder.length; i++) {
  1083. editorName = self.loadingHelper.loadOrder[i];
  1084. if(typeof self.loadingHelper[editorName] === 'function') {
  1085. self.loadingHelper[editorName](CKEDITOR.instances[editorName]);
  1086. }
  1087. delete self.loadingHelper[editorName];
  1088. }
  1089. self.loadingHelper.loadOrder = [];
  1090. }
  1091. });
  1092. this.loadingHelper.ckscaytLoading = true;
  1093. }
  1094. } else if(window.SCAYT && typeof window.SCAYT.CKSCAYT === 'function') {
  1095. CKEDITOR.fireOnce('scaytReady');
  1096. if(!editor.scayt) {
  1097. if(typeof callback === 'function') {
  1098. callback(editor);
  1099. }
  1100. }
  1101. }
  1102. }
  1103. };
  1104. CKEDITOR.on('dialogDefinition', function(dialogDefinitionEvent) {
  1105. var dialogName = dialogDefinitionEvent.data.name,
  1106. dialogDefinition = dialogDefinitionEvent.data.definition,
  1107. dialog = dialogDefinition.dialog;
  1108. if (dialogName === 'scaytDialog') {
  1109. dialog.on('cancel', function(cancelEvent) {
  1110. return false;
  1111. }, this, null, -1);
  1112. }
  1113. if ( dialogName === 'checkspell' ) {
  1114. dialog.on( 'cancel', function( cancelEvent ) {
  1115. var editor = cancelEvent.sender && cancelEvent.sender.getParentEditor(),
  1116. plugin = CKEDITOR.plugins.scayt,
  1117. scaytInstance = editor.scayt;
  1118. if ( scaytInstance && plugin.state.scayt[ editor.name ] && scaytInstance.setMarkupPaused ) {
  1119. scaytInstance.setMarkupPaused( false );
  1120. }
  1121. editor.unlockSelection();
  1122. }, this, null, -2 ); // we need to call cancel callback before WSC plugin
  1123. }
  1124. if (dialogName === 'link') {
  1125. dialog.on('ok', function(okEvent) {
  1126. var editor = okEvent.sender && okEvent.sender.getParentEditor();
  1127. if(editor) {
  1128. setTimeout(function() {
  1129. editor.fire('reloadMarkupScayt', {
  1130. removeOptions: {
  1131. removeInside: true,
  1132. forceBookmark: true
  1133. },
  1134. timeout: 0
  1135. });
  1136. }, 0);
  1137. }
  1138. });
  1139. }
  1140. });
  1141. CKEDITOR.on('scaytReady', function() {
  1142. // Override editor.checkDirty method avoid CK checkDirty functionality to fix SCAYT issues with incorrect checkDirty behavior.
  1143. if(CKEDITOR.config.scayt_handleCheckDirty === true) {
  1144. var editorCheckDirty = CKEDITOR.editor.prototype;
  1145. editorCheckDirty.checkDirty = CKEDITOR.tools.override(editorCheckDirty.checkDirty, function(org) {
  1146. return function() {
  1147. var retval = null,
  1148. pluginStatus = CKEDITOR.plugins.scayt && CKEDITOR.plugins.scayt.state.scayt[this.name] && this.scayt,
  1149. scaytInstance = this.scayt;
  1150. if(!pluginStatus) {
  1151. retval = org.call(this);
  1152. } else {
  1153. retval = (this.status == 'ready');
  1154. if (retval) {
  1155. var currentData = scaytInstance.removeMarkupFromString(this.getSnapshot()),
  1156. prevData = scaytInstance.removeMarkupFromString(this._.previousValue);
  1157. retval = (retval && (prevData !== currentData))
  1158. }
  1159. }
  1160. return retval;
  1161. };
  1162. });
  1163. editorCheckDirty.resetDirty = CKEDITOR.tools.override(editorCheckDirty.resetDirty, function(org) {
  1164. return function() {
  1165. var pluginStatus = CKEDITOR.plugins.scayt && CKEDITOR.plugins.scayt.state.scayt[this.name] && this.scayt,
  1166. scaytInstance = this.scayt;//CKEDITOR.plugins.scayt.getScayt(this);
  1167. if(!pluginStatus) {
  1168. org.call(this);
  1169. } else {
  1170. this._.previousValue = scaytInstance.removeMarkupFromString(this.getSnapshot());
  1171. }
  1172. };
  1173. });
  1174. }
  1175. if (CKEDITOR.config.scayt_handleUndoRedo === true) {
  1176. var undoImagePrototype = CKEDITOR.plugins.undo.Image.prototype;
  1177. // add backword compatibility for CKEDITOR 4.2. method equals was repleced on other method
  1178. var equalsContentMethodName = (typeof undoImagePrototype.equalsContent == "function") ? 'equalsContent' : 'equals';
  1179. undoImagePrototype[equalsContentMethodName] = CKEDITOR.tools.override(undoImagePrototype[equalsContentMethodName], function(org) {
  1180. return function(otherImage) {
  1181. var pluginState = CKEDITOR.plugins.scayt && CKEDITOR.plugins.scayt.state.scayt[otherImage.editor.name] && otherImage.editor.scayt,
  1182. scaytInstance = otherImage.editor.scayt,
  1183. thisContents = this.contents,
  1184. otherContents = otherImage.contents,
  1185. retval = null;
  1186. // Making the comparison based on content without SCAYT word markers.
  1187. if(pluginState) {
  1188. this.contents = scaytInstance.removeMarkupFromString(thisContents) || '';
  1189. otherImage.contents = scaytInstance.removeMarkupFromString(otherContents) || '';
  1190. }
  1191. var retval = org.apply(this, arguments);
  1192. this.contents = thisContents;
  1193. otherImage.contents = otherContents;
  1194. return retval;
  1195. };
  1196. });
  1197. }
  1198. });
  1199. /**
  1200. * Automatically enables SCAYT on editor startup. When set to `true`, this option turns on SCAYT automatically
  1201. * after loading the editor.
  1202. *
  1203. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1204. *
  1205. * config.scayt_autoStartup = true;
  1206. *
  1207. * @cfg {Boolean} [scayt_autoStartup=false]
  1208. * @member CKEDITOR.config
  1209. */
  1210. /**
  1211. * Enables Grammar As You Type (GRAYT) on SCAYT startup. When set to `true`, this option turns on GRAYT automatically
  1212. * after SCAYT started.
  1213. *
  1214. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1215. *
  1216. * config.grayt_autoStartup = true;
  1217. *
  1218. * @since 4.5.6
  1219. * @cfg {Boolean} [grayt_autoStartup=false]
  1220. * @member CKEDITOR.config
  1221. */
  1222. /**
  1223. * Enables SCAYT initialization when inline CKEditor is not focused. When set to `true`, SCAYT markup is
  1224. * displayed in both inline editor states, focused and unfocused, so the SCAYT instance is not destroyed.
  1225. *
  1226. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1227. *
  1228. * config.scayt_inlineModeImmediateMarkup = true;
  1229. *
  1230. * @since 4.5.6
  1231. * @cfg {Boolean} [scayt_inlineModeImmediateMarkup=false]
  1232. * @member CKEDITOR.config
  1233. */
  1234. /**
  1235. * Defines the number of SCAYT suggestions to show in the main context menu.
  1236. * Possible values are:
  1237. *
  1238. * * `0` (zero) &ndash; No suggestions are shown in the main context menu. All
  1239. * entries will be listed in the "More Suggestions" sub-menu.
  1240. * * Positive number &ndash; The maximum number of suggestions to show in the context
  1241. * menu. Other entries will be shown in the "More Suggestions" sub-menu.
  1242. * * Negative number &ndash; Five suggestions are shown in the main context menu. All other
  1243. * entries will be listed in the "More Suggestions" sub-menu.
  1244. *
  1245. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1246. *
  1247. * Examples:
  1248. *
  1249. * // Display only three suggestions in the main context menu.
  1250. * config.scayt_maxSuggestions = 3;
  1251. *
  1252. * // Do not show the suggestions directly.
  1253. * config.scayt_maxSuggestions = 0;
  1254. *
  1255. * @cfg {Number} [scayt_maxSuggestions=5]
  1256. * @member CKEDITOR.config
  1257. */
  1258. /**
  1259. * Defines the minimum length of words that will be collected from the editor content for spell checking.
  1260. * Possible value is any positive number.
  1261. *
  1262. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1263. *
  1264. * Examples:
  1265. *
  1266. * // Set the minimum length of words that will be collected from editor text.
  1267. * config.scayt_minWordLength = 5;
  1268. *
  1269. * @cfg {Number} [scayt_minWordLength=4]
  1270. * @member CKEDITOR.config
  1271. */
  1272. /**
  1273. * Sets the customer ID for SCAYT. Used for hosted users only. Required for migration from free
  1274. * to trial or paid versions.
  1275. *
  1276. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1277. *
  1278. * // Load SCAYT using my customer ID.
  1279. * config.scayt_customerId = 'your-encrypted-customer-id';
  1280. *
  1281. * @cfg {String} [scayt_customerId='1:WvF0D4-UtPqN1-43nkD4-NKvUm2-daQqk3-LmNiI-z7Ysb4-mwry24-T8YrS3-Q2tpq2']
  1282. * @member CKEDITOR.config
  1283. */
  1284. /**
  1285. * Enables and disables the "More Suggestions" sub-menu in the context menu.
  1286. * Possible values are `'on'` and `'off'`.
  1287. *
  1288. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1289. *
  1290. * // Disables the "More Suggestions" sub-menu.
  1291. * config.scayt_moreSuggestions = 'off';
  1292. *
  1293. * @cfg {String} [scayt_moreSuggestions='on']
  1294. * @member CKEDITOR.config
  1295. */
  1296. /**
  1297. * Customizes the display of SCAYT context menu commands ("Add Word", "Ignore",
  1298. * "Ignore All", "Options", "Languages", "Dictionaries" and "About").
  1299. * This must be a string with one or more of the following
  1300. * words separated by a pipe character (`'|'`):
  1301. *
  1302. * * `off` &ndash; Disables all options.
  1303. * * `all` &ndash; Enables all options.
  1304. * * `ignore` &ndash; Enables the "Ignore" option.
  1305. * * `ignoreall` &ndash; Enables the "Ignore All" option.
  1306. * * `add` &ndash; Enables the "Add Word" option.
  1307. * * `option` &ndash; Enables the "Options" menu item.
  1308. * * `language` &ndash; Enables the "Languages" menu item.
  1309. * * `dictionary` &ndash; Enables the "Dictionaries" menu item.
  1310. * * `about` &ndash; Enables the "About" menu item.
  1311. *
  1312. * Please note that availability of the "Options", "Languages" and "Dictionaries" items
  1313. * also depends on the {@link CKEDITOR.config#scayt_uiTabs} option.
  1314. *
  1315. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1316. *
  1317. * Example:
  1318. *
  1319. * // Show only "Add Word" and "Ignore All" in the context menu.
  1320. * config.scayt_contextCommands = 'add|ignoreall';
  1321. *
  1322. * @cfg {String} [scayt_contextCommands='ignore|ignoreall|add']
  1323. * @member CKEDITOR.config
  1324. */
  1325. /**
  1326. * Sets the default spell checking language for SCAYT. Possible values are:
  1327. * `'en_US'`, `'en_GB'`, `'pt_BR'`, `'da_DK'`,
  1328. * `'nl_NL'`, `'en_CA'`, `'fi_FI'`, `'fr_FR'`,
  1329. * `'fr_CA'`, `'de_DE'`, `'el_GR'`, `'it_IT'`,
  1330. * `'nb_NO'`, `'pt_PT'`, `'es_ES'`, `'sv_SE'`.
  1331. *
  1332. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1333. *
  1334. * // Sets SCAYT to German.
  1335. * config.scayt_sLang = 'de_DE';
  1336. *
  1337. * @cfg {String} [scayt_sLang='en_US']
  1338. * @member CKEDITOR.config
  1339. */
  1340. /**
  1341. * Customizes the SCAYT dialog and SCAYT toolbar menu to show particular tabs and items.
  1342. * This setting must contain a `1` (enabled) or `0`
  1343. * (disabled) value for each of the following entries, in this precise order,
  1344. * separated by a comma (`','`): `'Options'`, `'Languages'`, and `'Dictionary'`.
  1345. *
  1346. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1347. *
  1348. * // Hides the "Languages" tab.
  1349. * config.scayt_uiTabs = '1,0,1';
  1350. *
  1351. * @cfg {String} [scayt_uiTabs='1,1,1']
  1352. * @member CKEDITOR.config
  1353. */
  1354. /**
  1355. * Sets the protocol for the WebSpellChecker service (`ssrv.cgi`) full path.
  1356. *
  1357. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1358. *
  1359. * // Defines the protocol for the WebSpellChecker service (ssrv.cgi) path.
  1360. * config.scayt_serviceProtocol = 'https';
  1361. *
  1362. * @cfg {String} [scayt_serviceProtocol='http']
  1363. * @member CKEDITOR.config
  1364. */
  1365. /**
  1366. * Sets the host for the WebSpellChecker service (`ssrv.cgi`) full path.
  1367. *
  1368. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1369. *
  1370. * // Defines the host for the WebSpellChecker service (ssrv.cgi) path.
  1371. * config.scayt_serviceHost = 'my-host';
  1372. *
  1373. * @cfg {String} [scayt_serviceHost='svc.webspellchecker.net']
  1374. * @member CKEDITOR.config
  1375. */
  1376. /**
  1377. * Sets the port for the WebSpellChecker service (`ssrv.cgi`) full path.
  1378. *
  1379. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1380. *
  1381. * // Defines the port for the WebSpellChecker service (ssrv.cgi) path.
  1382. * config.scayt_servicePort = '2330';
  1383. *
  1384. * @cfg {String} [scayt_servicePort='80']
  1385. * @member CKEDITOR.config
  1386. */
  1387. /**
  1388. * Sets the path to the WebSpellChecker service (`ssrv.cgi`).
  1389. *
  1390. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1391. *
  1392. * // Defines the path to the WebSpellChecker service (ssrv.cgi).
  1393. * config.scayt_servicePath = 'my-path/ssrv.cgi';
  1394. *
  1395. * @cfg {String} [scayt_servicePath='spellcheck31/script/ssrv.cgi']
  1396. * @member CKEDITOR.config
  1397. */
  1398. /**
  1399. * Sets the URL to SCAYT core. Required to switch to the licensed version of SCAYT.
  1400. *
  1401. * Refer to [SCAYT documentation](http://wiki.webspellchecker.net/doku.php?id=migration:hosredfreetolicensedck)
  1402. * for more details.
  1403. *
  1404. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1405. *
  1406. * config.scayt_srcUrl = "http://my-host/spellcheck/lf/scayt/scayt.js";
  1407. *
  1408. * @cfg {String} [scayt_srcUrl='//svc.webspellchecker.net/spellcheck31/lf/scayt3/ckscayt/ckscayt.js']
  1409. * @member CKEDITOR.config
  1410. */
  1411. /**
  1412. * Links SCAYT to custom dictionaries. This is a string containing the dictionary IDs
  1413. * separated by commas (`','`). Available only for the licensed version.
  1414. *
  1415. * Refer to [SCAYT documentation](http://wiki.webspellchecker.net/doku.php?id=installationandconfiguration:customdictionaries:licensed)
  1416. * for more details.
  1417. *
  1418. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1419. *
  1420. * config.scayt_customDictionaryIds = '3021,3456,3478';
  1421. *
  1422. * @cfg {String} [scayt_customDictionaryIds='']
  1423. * @member CKEDITOR.config
  1424. */
  1425. /**
  1426. * Activates a User Dictionary in SCAYT. The user
  1427. * dictionary name must be used. Available only for the licensed version.
  1428. *
  1429. * Refer to [SCAYT documentation](http://wiki.webspellchecker.net/doku.php?id=installationandconfiguration:userdictionaries)
  1430. * for more details.
  1431. *
  1432. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1433. *
  1434. * config.scayt_userDictionaryName = 'MyDictionary';
  1435. *
  1436. * @cfg {String} [scayt_userDictionaryName='']
  1437. * @member CKEDITOR.config
  1438. */
  1439. /**
  1440. * Defines the order of SCAYT context menu items by groups.
  1441. * This must be a string with one or more of the following
  1442. * words separated by a pipe character (`'|'`):
  1443. *
  1444. * * `suggest` &ndash; The main suggestion word list.
  1445. * * `moresuggest` &ndash; The "More suggestions" word list.
  1446. * * `control` &ndash; SCAYT commands, such as "Ignore" and "Add Word".
  1447. *
  1448. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1449. *
  1450. * Example:
  1451. *
  1452. * config.scayt_contextMenuItemsOrder = 'moresuggest|control|suggest';
  1453. *
  1454. * @cfg {String} [scayt_contextMenuItemsOrder='suggest|moresuggest|control']
  1455. * @member CKEDITOR.config
  1456. */
  1457. /**
  1458. * If set to `true`, it overrides the {@link CKEDITOR.editor#checkDirty checkDirty} functionality of CKEditor
  1459. * to fix SCAYT issues with incorrect `checkDirty` behavior. If set to `false`,
  1460. * it provides better performance on big preloaded text.
  1461. *
  1462. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1463. *
  1464. * config.scayt_handleCheckDirty = 'false';
  1465. *
  1466. * @cfg {String} [scayt_handleCheckDirty='true']
  1467. * @member CKEDITOR.config
  1468. */
  1469. /**
  1470. * Configures undo/redo behavior of SCAYT in CKEditor.
  1471. * If set to `true`, it overrides the undo/redo functionality of CKEditor
  1472. * to fix SCAYT issues with incorrect undo/redo behavior. If set to `false`,
  1473. * it provides better performance on text undo/redo.
  1474. *
  1475. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1476. *
  1477. * config.scayt_handleUndoRedo = 'false';
  1478. *
  1479. * @cfg {String} [scayt_handleUndoRedo='true']
  1480. * @member CKEDITOR.config
  1481. */
  1482. /**
  1483. * Enables the "Ignore All-Caps Words" option by default.
  1484. * You may need to {@link CKEDITOR.config#scayt_disableOptionsStorage disable option storing} for this setting to be
  1485. * effective because option storage has a higher priority.
  1486. *
  1487. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1488. *
  1489. * config.scayt_ignoreAllCapsWords = true;
  1490. *
  1491. * @since 4.5.6
  1492. * @cfg {Boolean} [scayt_ignoreAllCapsWords=false]
  1493. * @member CKEDITOR.config
  1494. */
  1495. /**
  1496. * Enables the "Ignore Domain Names" option by default.
  1497. * You may need to {@link CKEDITOR.config#scayt_disableOptionsStorage disable option storing} for this setting to be
  1498. * effective because option storage has a higher priority.
  1499. *
  1500. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1501. *
  1502. * config.scayt_ignoreDomainNames = true;
  1503. *
  1504. * @since 4.5.6
  1505. * @cfg {Boolean} [scayt_ignoreDomainNames=false]
  1506. * @member CKEDITOR.config
  1507. */
  1508. /**
  1509. * Enables the "Ignore Words with Mixed Case" option by default.
  1510. * You may need to {@link CKEDITOR.config#scayt_disableOptionsStorage disable option storing} for this setting to be
  1511. * effective because option storage has a higher priority.
  1512. *
  1513. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1514. *
  1515. * config.scayt_ignoreWordsWithMixedCases = true;
  1516. *
  1517. * @since 4.5.6
  1518. * @cfg {Boolean} [scayt_ignoreWordsWithMixedCases=false]
  1519. * @member CKEDITOR.config
  1520. */
  1521. /**
  1522. * Enables the "Ignore Words with Numbers" option by default.
  1523. * You may need to {@link CKEDITOR.config#scayt_disableOptionsStorage disable option storing} for this setting to be
  1524. * effective because option storage has a higher priority.
  1525. *
  1526. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1527. *
  1528. * config.scayt_ignoreWordsWithNumbers = true;
  1529. *
  1530. * @since 4.5.6
  1531. * @cfg {Boolean} [scayt_ignoreWordsWithNumbers=false]
  1532. * @member CKEDITOR.config
  1533. */
  1534. /**
  1535. * Disables storing of SCAYT options between sessions. Option storing will be turned off after a page refresh.
  1536. * The following settings can be used:
  1537. *
  1538. * * `'options'` &ndash; Disables storing of all SCAYT Ignore options.
  1539. * * `'ignore-all-caps-words'` &ndash; Disables storing of the "Ignore All-Caps Words" option.
  1540. * * `'ignore-domain-names'` &ndash; Disables storing of the "Ignore Domain Names" option.
  1541. * * `'ignore-words-with-mixed-cases'` &ndash; Disables storing of the "Ignore Words with Mixed Case" option.
  1542. * * `'ignore-words-with-numbers'` &ndash; Disables storing of the "Ignore Words with Numbers" option.
  1543. * * `'lang'` &ndash; Disables storing of the SCAYT spell check language.
  1544. * * `'all'` &ndash; Disables storing of all SCAYT options.
  1545. *
  1546. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1547. *
  1548. * Example:
  1549. *
  1550. * // Disabling one option.
  1551. * config.scayt_disableOptionsStorage = 'all';
  1552. *
  1553. * // Disabling several options.
  1554. * config.scayt_disableOptionsStorage = ['lang', 'ignore-domain-names', 'ignore-words-with-numbers'];
  1555. *
  1556. *
  1557. * @cfg {String|Array} [scayt_disableOptionsStorage = '']
  1558. * @member CKEDITOR.config
  1559. */
  1560. /**
  1561. * Specifies the names of tags that will be skipped while spell checking. This is a string containing tag names
  1562. * separated by commas (`','`). Please note that the `'style'` tag would be added to specified tags list.
  1563. *
  1564. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1565. *
  1566. * config.scayt_elementsToIgnore = 'del,pre';
  1567. *
  1568. * @cfg {String} [scayt_elementsToIgnore='style']
  1569. * @member CKEDITOR.config
  1570. */
  1571. /**
  1572. * Enables multi-language support in SCAYT. If set to `true`, turns on SCAYT multi-language support after loading the editor.
  1573. *
  1574. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1575. *
  1576. * config.scayt_multiLanguageMode = true;
  1577. *
  1578. * @cfg {Boolean} [scayt_multiLanguageMode=false]
  1579. * @member CKEDITOR.config
  1580. */
  1581. /**
  1582. * Defines additional styles for misspellings for specified languages. Styles will be applied only if
  1583. * the {@link CKEDITOR.config#scayt_multiLanguageMode} option is set to `true` and the [Language](http://ckeditor.com/addon/language)
  1584. * plugin is included and loaded in the editor. By default, all misspellings will still be underlined with the red waveline.
  1585. *
  1586. * Read more in the [documentation](#!/guide/dev_spellcheck) and see the [SDK sample](http://sdk.ckeditor.com/samples/spellchecker.html).
  1587. *
  1588. * Example:
  1589. *
  1590. * // Display misspellings in French language with green color and underlined with red waveline.
  1591. * config.scayt_multiLanguageStyles = {
  1592. * 'fr': 'color: green'
  1593. * };
  1594. *
  1595. * // Display misspellings in Italian language with green color and underlined with red waveline
  1596. * // and German misspellings with red color only.
  1597. * config.scayt_multiLanguageStyles = {
  1598. * 'it': 'color: green',
  1599. * 'de': 'background-image: none; color: red'
  1600. * };
  1601. *
  1602. * @cfg {Object} [scayt_multiLanguageStyles = {}]
  1603. * @member CKEDITOR.config
  1604. */