mirador_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +13 -0
  7. data/README.md +97 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/lib/mirador_rails.rb +16 -0
  12. data/lib/mirador_rails/version.rb +3 -0
  13. data/lib/mirador_rails/view_helpers.rb +30 -0
  14. data/mirador_rails.gemspec +30 -0
  15. data/vendor/assets/images/border_type_1.png +0 -0
  16. data/vendor/assets/images/border_type_2.png +0 -0
  17. data/vendor/assets/images/border_type_3.png +0 -0
  18. data/vendor/assets/images/debut_dark.png +0 -0
  19. data/vendor/assets/javascripts/mirador.js +1 -0
  20. data/vendor/assets/javascripts/mirador.min.js +88 -0
  21. data/vendor/assets/javascripts/mirador.min.js.map +1 -0
  22. data/vendor/assets/locales/ar/translation.json +42 -0
  23. data/vendor/assets/locales/de/translation.json +77 -0
  24. data/vendor/assets/locales/en/translation.json +82 -0
  25. data/vendor/assets/locales/es/translation.json +35 -0
  26. data/vendor/assets/locales/fr/translation.json +77 -0
  27. data/vendor/assets/locales/ga/translation.json +39 -0
  28. data/vendor/assets/locales/ja/translation.json +82 -0
  29. data/vendor/assets/locales/ko/translation.json +74 -0
  30. data/vendor/assets/locales/nl/translation.json +39 -0
  31. data/vendor/assets/locales/zh-CN/translation.json +74 -0
  32. data/vendor/assets/locales/zh-TW/translation.json +74 -0
  33. data/vendor/assets/locales/zh/translation.json +74 -0
  34. data/vendor/assets/plugins/plugins/advlist/plugin.js +97 -0
  35. data/vendor/assets/plugins/plugins/advlist/plugin.min.js +1 -0
  36. data/vendor/assets/plugins/plugins/anchor/plugin.js +45 -0
  37. data/vendor/assets/plugins/plugins/anchor/plugin.min.js +1 -0
  38. data/vendor/assets/plugins/plugins/autolink/plugin.js +194 -0
  39. data/vendor/assets/plugins/plugins/autolink/plugin.min.js +1 -0
  40. data/vendor/assets/plugins/plugins/autoresize/plugin.js +152 -0
  41. data/vendor/assets/plugins/plugins/autoresize/plugin.min.js +1 -0
  42. data/vendor/assets/plugins/plugins/autosave/plugin.js +165 -0
  43. data/vendor/assets/plugins/plugins/autosave/plugin.min.js +1 -0
  44. data/vendor/assets/plugins/plugins/bbcode/plugin.js +123 -0
  45. data/vendor/assets/plugins/plugins/bbcode/plugin.min.js +1 -0
  46. data/vendor/assets/plugins/plugins/charmap/plugin.js +370 -0
  47. data/vendor/assets/plugins/plugins/charmap/plugin.min.js +1 -0
  48. data/vendor/assets/plugins/plugins/code/plugin.js +60 -0
  49. data/vendor/assets/plugins/plugins/code/plugin.min.js +1 -0
  50. data/vendor/assets/plugins/plugins/colorpicker/plugin.js +112 -0
  51. data/vendor/assets/plugins/plugins/colorpicker/plugin.min.js +1 -0
  52. data/vendor/assets/plugins/plugins/compat3x/css/dialog.css +118 -0
  53. data/vendor/assets/plugins/plugins/compat3x/img/buttons.png +0 -0
  54. data/vendor/assets/plugins/plugins/compat3x/img/icons.gif +0 -0
  55. data/vendor/assets/plugins/plugins/compat3x/img/items.gif +0 -0
  56. data/vendor/assets/plugins/plugins/compat3x/img/menu_arrow.gif +0 -0
  57. data/vendor/assets/plugins/plugins/compat3x/img/menu_check.gif +0 -0
  58. data/vendor/assets/plugins/plugins/compat3x/img/progress.gif +0 -0
  59. data/vendor/assets/plugins/plugins/compat3x/img/tabs.gif +0 -0
  60. data/vendor/assets/plugins/plugins/compat3x/plugin.js +297 -0
  61. data/vendor/assets/plugins/plugins/compat3x/plugin.min.js +1 -0
  62. data/vendor/assets/plugins/plugins/compat3x/tiny_mce_popup.js +542 -0
  63. data/vendor/assets/plugins/plugins/compat3x/utils/editable_selects.js +70 -0
  64. data/vendor/assets/plugins/plugins/compat3x/utils/form_utils.js +210 -0
  65. data/vendor/assets/plugins/plugins/compat3x/utils/mctabs.js +164 -0
  66. data/vendor/assets/plugins/plugins/compat3x/utils/validate.js +252 -0
  67. data/vendor/assets/plugins/plugins/contextmenu/plugin.js +87 -0
  68. data/vendor/assets/plugins/plugins/contextmenu/plugin.min.js +1 -0
  69. data/vendor/assets/plugins/plugins/directionality/plugin.js +64 -0
  70. data/vendor/assets/plugins/plugins/directionality/plugin.min.js +1 -0
  71. data/vendor/assets/plugins/plugins/emoticons/img/smiley-cool.gif +0 -0
  72. data/vendor/assets/plugins/plugins/emoticons/img/smiley-cry.gif +0 -0
  73. data/vendor/assets/plugins/plugins/emoticons/img/smiley-embarassed.gif +0 -0
  74. data/vendor/assets/plugins/plugins/emoticons/img/smiley-foot-in-mouth.gif +0 -0
  75. data/vendor/assets/plugins/plugins/emoticons/img/smiley-frown.gif +0 -0
  76. data/vendor/assets/plugins/plugins/emoticons/img/smiley-innocent.gif +0 -0
  77. data/vendor/assets/plugins/plugins/emoticons/img/smiley-kiss.gif +0 -0
  78. data/vendor/assets/plugins/plugins/emoticons/img/smiley-laughing.gif +0 -0
  79. data/vendor/assets/plugins/plugins/emoticons/img/smiley-money-mouth.gif +0 -0
  80. data/vendor/assets/plugins/plugins/emoticons/img/smiley-sealed.gif +0 -0
  81. data/vendor/assets/plugins/plugins/emoticons/img/smiley-smile.gif +0 -0
  82. data/vendor/assets/plugins/plugins/emoticons/img/smiley-surprised.gif +0 -0
  83. data/vendor/assets/plugins/plugins/emoticons/img/smiley-tongue-out.gif +0 -0
  84. data/vendor/assets/plugins/plugins/emoticons/img/smiley-undecided.gif +0 -0
  85. data/vendor/assets/plugins/plugins/emoticons/img/smiley-wink.gif +0 -0
  86. data/vendor/assets/plugins/plugins/emoticons/img/smiley-yell.gif +0 -0
  87. data/vendor/assets/plugins/plugins/emoticons/plugin.js +65 -0
  88. data/vendor/assets/plugins/plugins/emoticons/plugin.min.js +1 -0
  89. data/vendor/assets/plugins/plugins/example/dialog.html +8 -0
  90. data/vendor/assets/plugins/plugins/example/plugin.js +68 -0
  91. data/vendor/assets/plugins/plugins/example/plugin.min.js +1 -0
  92. data/vendor/assets/plugins/plugins/example_dependency/plugin.js +22 -0
  93. data/vendor/assets/plugins/plugins/example_dependency/plugin.min.js +1 -0
  94. data/vendor/assets/plugins/plugins/fullpage/plugin.js +490 -0
  95. data/vendor/assets/plugins/plugins/fullpage/plugin.min.js +1 -0
  96. data/vendor/assets/plugins/plugins/fullscreen/plugin.js +136 -0
  97. data/vendor/assets/plugins/plugins/fullscreen/plugin.min.js +1 -0
  98. data/vendor/assets/plugins/plugins/hr/plugin.js +30 -0
  99. data/vendor/assets/plugins/plugins/hr/plugin.min.js +1 -0
  100. data/vendor/assets/plugins/plugins/image/plugin.js +439 -0
  101. data/vendor/assets/plugins/plugins/image/plugin.min.js +1 -0
  102. data/vendor/assets/plugins/plugins/importcss/plugin.js +195 -0
  103. data/vendor/assets/plugins/plugins/importcss/plugin.min.js +1 -0
  104. data/vendor/assets/plugins/plugins/insertdatetime/plugin.js +121 -0
  105. data/vendor/assets/plugins/plugins/insertdatetime/plugin.min.js +1 -0
  106. data/vendor/assets/plugins/plugins/layer/plugin.js +225 -0
  107. data/vendor/assets/plugins/plugins/layer/plugin.min.js +1 -0
  108. data/vendor/assets/plugins/plugins/legacyoutput/plugin.js +211 -0
  109. data/vendor/assets/plugins/plugins/legacyoutput/plugin.min.js +1 -0
  110. data/vendor/assets/plugins/plugins/link/plugin.js +400 -0
  111. data/vendor/assets/plugins/plugins/link/plugin.min.js +1 -0
  112. data/vendor/assets/plugins/plugins/lists/plugin.js +791 -0
  113. data/vendor/assets/plugins/plugins/lists/plugin.min.js +1 -0
  114. data/vendor/assets/plugins/plugins/media/moxieplayer.swf +0 -0
  115. data/vendor/assets/plugins/plugins/media/plugin.js +774 -0
  116. data/vendor/assets/plugins/plugins/media/plugin.min.js +1 -0
  117. data/vendor/assets/plugins/plugins/nonbreaking/plugin.js +53 -0
  118. data/vendor/assets/plugins/plugins/nonbreaking/plugin.min.js +1 -0
  119. data/vendor/assets/plugins/plugins/noneditable/plugin.js +540 -0
  120. data/vendor/assets/plugins/plugins/noneditable/plugin.min.js +1 -0
  121. data/vendor/assets/plugins/plugins/pagebreak/plugin.js +88 -0
  122. data/vendor/assets/plugins/plugins/pagebreak/plugin.min.js +1 -0
  123. data/vendor/assets/plugins/plugins/paste/classes/Clipboard.js +634 -0
  124. data/vendor/assets/plugins/plugins/paste/classes/Plugin.js +110 -0
  125. data/vendor/assets/plugins/plugins/paste/classes/Quirks.js +159 -0
  126. data/vendor/assets/plugins/plugins/paste/classes/Utils.js +130 -0
  127. data/vendor/assets/plugins/plugins/paste/classes/WordFilter.js +493 -0
  128. data/vendor/assets/plugins/plugins/paste/plugin.dev.js +120 -0
  129. data/vendor/assets/plugins/plugins/paste/plugin.js +1625 -0
  130. data/vendor/assets/plugins/plugins/paste/plugin.min.js +1 -0
  131. data/vendor/assets/plugins/plugins/preview/plugin.js +88 -0
  132. data/vendor/assets/plugins/plugins/preview/plugin.min.js +1 -0
  133. data/vendor/assets/plugins/plugins/print/plugin.js +32 -0
  134. data/vendor/assets/plugins/plugins/print/plugin.min.js +1 -0
  135. data/vendor/assets/plugins/plugins/save/plugin.js +94 -0
  136. data/vendor/assets/plugins/plugins/save/plugin.min.js +1 -0
  137. data/vendor/assets/plugins/plugins/searchreplace/plugin.js +594 -0
  138. data/vendor/assets/plugins/plugins/searchreplace/plugin.min.js +1 -0
  139. data/vendor/assets/plugins/plugins/spellchecker/classes/DomTextMatcher.js +470 -0
  140. data/vendor/assets/plugins/plugins/spellchecker/classes/Plugin.js +436 -0
  141. data/vendor/assets/plugins/plugins/spellchecker/plugin.dev.js +117 -0
  142. data/vendor/assets/plugins/plugins/spellchecker/plugin.js +996 -0
  143. data/vendor/assets/plugins/plugins/spellchecker/plugin.min.js +1 -0
  144. data/vendor/assets/plugins/plugins/tabfocus/plugin.js +120 -0
  145. data/vendor/assets/plugins/plugins/tabfocus/plugin.min.js +1 -0
  146. data/vendor/assets/plugins/plugins/table/classes/CellSelection.js +176 -0
  147. data/vendor/assets/plugins/plugins/table/classes/Dialogs.js +749 -0
  148. data/vendor/assets/plugins/plugins/table/classes/Plugin.js +422 -0
  149. data/vendor/assets/plugins/plugins/table/classes/Quirks.js +372 -0
  150. data/vendor/assets/plugins/plugins/table/classes/TableGrid.js +864 -0
  151. data/vendor/assets/plugins/plugins/table/plugin.dev.js +118 -0
  152. data/vendor/assets/plugins/plugins/table/plugin.js +2680 -0
  153. data/vendor/assets/plugins/plugins/table/plugin.min.js +1 -0
  154. data/vendor/assets/plugins/plugins/template/plugin.js +262 -0
  155. data/vendor/assets/plugins/plugins/template/plugin.min.js +1 -0
  156. data/vendor/assets/plugins/plugins/textcolor/plugin.js +272 -0
  157. data/vendor/assets/plugins/plugins/textcolor/plugin.min.js +1 -0
  158. data/vendor/assets/plugins/plugins/textpattern/plugin.js +268 -0
  159. data/vendor/assets/plugins/plugins/textpattern/plugin.min.js +1 -0
  160. data/vendor/assets/plugins/plugins/visualblocks/css/visualblocks.css +135 -0
  161. data/vendor/assets/plugins/plugins/visualblocks/img/address.gif +0 -0
  162. data/vendor/assets/plugins/plugins/visualblocks/img/article.gif +0 -0
  163. data/vendor/assets/plugins/plugins/visualblocks/img/aside.gif +0 -0
  164. data/vendor/assets/plugins/plugins/visualblocks/img/blockquote.gif +0 -0
  165. data/vendor/assets/plugins/plugins/visualblocks/img/div.gif +0 -0
  166. data/vendor/assets/plugins/plugins/visualblocks/img/dl.gif +0 -0
  167. data/vendor/assets/plugins/plugins/visualblocks/img/figure.gif +0 -0
  168. data/vendor/assets/plugins/plugins/visualblocks/img/h1.gif +0 -0
  169. data/vendor/assets/plugins/plugins/visualblocks/img/h2.gif +0 -0
  170. data/vendor/assets/plugins/plugins/visualblocks/img/h3.gif +0 -0
  171. data/vendor/assets/plugins/plugins/visualblocks/img/h4.gif +0 -0
  172. data/vendor/assets/plugins/plugins/visualblocks/img/h5.gif +0 -0
  173. data/vendor/assets/plugins/plugins/visualblocks/img/h6.gif +0 -0
  174. data/vendor/assets/plugins/plugins/visualblocks/img/hgroup.gif +0 -0
  175. data/vendor/assets/plugins/plugins/visualblocks/img/ol.gif +0 -0
  176. data/vendor/assets/plugins/plugins/visualblocks/img/p.gif +0 -0
  177. data/vendor/assets/plugins/plugins/visualblocks/img/pre.gif +0 -0
  178. data/vendor/assets/plugins/plugins/visualblocks/img/section.gif +0 -0
  179. data/vendor/assets/plugins/plugins/visualblocks/img/ul.gif +0 -0
  180. data/vendor/assets/plugins/plugins/visualblocks/plugin.js +86 -0
  181. data/vendor/assets/plugins/plugins/visualblocks/plugin.min.js +1 -0
  182. data/vendor/assets/plugins/plugins/visualchars/plugin.js +88 -0
  183. data/vendor/assets/plugins/plugins/visualchars/plugin.min.js +1 -0
  184. data/vendor/assets/plugins/plugins/wordcount/plugin.js +69 -0
  185. data/vendor/assets/plugins/plugins/wordcount/plugin.min.js +1 -0
  186. data/vendor/assets/skins/skins/lightgray/content.inline.min.css +1 -0
  187. data/vendor/assets/skins/skins/lightgray/content.min.css +1 -0
  188. data/vendor/assets/skins/skins/lightgray/fonts/tinymce-small.eot +0 -0
  189. data/vendor/assets/skins/skins/lightgray/fonts/tinymce-small.svg +62 -0
  190. data/vendor/assets/skins/skins/lightgray/fonts/tinymce-small.ttf +0 -0
  191. data/vendor/assets/skins/skins/lightgray/fonts/tinymce-small.woff +0 -0
  192. data/vendor/assets/skins/skins/lightgray/fonts/tinymce.eot +0 -0
  193. data/vendor/assets/skins/skins/lightgray/fonts/tinymce.svg +63 -0
  194. data/vendor/assets/skins/skins/lightgray/fonts/tinymce.ttf +0 -0
  195. data/vendor/assets/skins/skins/lightgray/fonts/tinymce.woff +0 -0
  196. data/vendor/assets/skins/skins/lightgray/img/anchor.gif +0 -0
  197. data/vendor/assets/skins/skins/lightgray/img/loader.gif +0 -0
  198. data/vendor/assets/skins/skins/lightgray/img/object.gif +0 -0
  199. data/vendor/assets/skins/skins/lightgray/img/trans.gif +0 -0
  200. data/vendor/assets/skins/skins/lightgray/skin.ie7.min.css +1 -0
  201. data/vendor/assets/skins/skins/lightgray/skin.min.css +1 -0
  202. data/vendor/assets/stylesheets/mirador-combined.css +3969 -0
  203. data/vendor/assets/stylesheets/mirador.css +11 -0
  204. data/vendor/assets/themes/themes/modern/theme.js +617 -0
  205. data/vendor/assets/themes/themes/modern/theme.min.js +1 -0
  206. metadata +318 -0
@@ -0,0 +1,996 @@
1
+ /**
2
+ * Compiled inline version. (Library mode)
3
+ */
4
+
5
+ /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
6
+ /*globals $code */
7
+
8
+ (function(exports, undefined) {
9
+ "use strict";
10
+
11
+ var modules = {};
12
+
13
+ function require(ids, callback) {
14
+ var module, defs = [];
15
+
16
+ for (var i = 0; i < ids.length; ++i) {
17
+ module = modules[ids[i]] || resolve(ids[i]);
18
+ if (!module) {
19
+ throw 'module definition dependecy not found: ' + ids[i];
20
+ }
21
+
22
+ defs.push(module);
23
+ }
24
+
25
+ callback.apply(null, defs);
26
+ }
27
+
28
+ function define(id, dependencies, definition) {
29
+ if (typeof id !== 'string') {
30
+ throw 'invalid module definition, module id must be defined and be a string';
31
+ }
32
+
33
+ if (dependencies === undefined) {
34
+ throw 'invalid module definition, dependencies must be specified';
35
+ }
36
+
37
+ if (definition === undefined) {
38
+ throw 'invalid module definition, definition function must be specified';
39
+ }
40
+
41
+ require(dependencies, function() {
42
+ modules[id] = definition.apply(null, arguments);
43
+ });
44
+ }
45
+
46
+ function defined(id) {
47
+ return !!modules[id];
48
+ }
49
+
50
+ function resolve(id) {
51
+ var target = exports;
52
+ var fragments = id.split(/[.\/]/);
53
+
54
+ for (var fi = 0; fi < fragments.length; ++fi) {
55
+ if (!target[fragments[fi]]) {
56
+ return;
57
+ }
58
+
59
+ target = target[fragments[fi]];
60
+ }
61
+
62
+ return target;
63
+ }
64
+
65
+ function expose(ids) {
66
+ for (var i = 0; i < ids.length; i++) {
67
+ var target = exports;
68
+ var id = ids[i];
69
+ var fragments = id.split(/[.\/]/);
70
+
71
+ for (var fi = 0; fi < fragments.length - 1; ++fi) {
72
+ if (target[fragments[fi]] === undefined) {
73
+ target[fragments[fi]] = {};
74
+ }
75
+
76
+ target = target[fragments[fi]];
77
+ }
78
+
79
+ target[fragments[fragments.length - 1]] = modules[id];
80
+ }
81
+ }
82
+
83
+ // Included from: js/tinymce/plugins/spellchecker/classes/DomTextMatcher.js
84
+
85
+ /**
86
+ * DomTextMatcher.js
87
+ *
88
+ * Copyright, Moxiecode Systems AB
89
+ * Released under LGPL License.
90
+ *
91
+ * License: http://www.tinymce.com/license
92
+ * Contributing: http://www.tinymce.com/contributing
93
+ */
94
+
95
+ /*eslint no-labels:0, no-constant-condition: 0 */
96
+
97
+ /**
98
+ * This class logic for filtering text and matching words.
99
+ *
100
+ * @class tinymce.spellcheckerplugin.TextFilter
101
+ * @private
102
+ */
103
+ define("tinymce/spellcheckerplugin/DomTextMatcher", [], function() {
104
+ // Based on work developed by: James Padolsey http://james.padolsey.com
105
+ // released under UNLICENSE that is compatible with LGPL
106
+ // TODO: Handle contentEditable edgecase:
107
+ // <p>text<span contentEditable="false">text<span contentEditable="true">text</span>text</span>text</p>
108
+ return function(node, editor) {
109
+ var m, matches = [], text, dom = editor.dom;
110
+ var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
111
+
112
+ blockElementsMap = editor.schema.getBlockElements(); // H1-H6, P, TD etc
113
+ hiddenTextElementsMap = editor.schema.getWhiteSpaceElements(); // TEXTAREA, PRE, STYLE, SCRIPT
114
+ shortEndedElementsMap = editor.schema.getShortEndedElements(); // BR, IMG, INPUT
115
+
116
+ function createMatch(m, data) {
117
+ if (!m[0]) {
118
+ throw 'findAndReplaceDOMText cannot handle zero-length matches';
119
+ }
120
+
121
+ return {
122
+ start: m.index,
123
+ end: m.index + m[0].length,
124
+ text: m[0],
125
+ data: data
126
+ };
127
+ }
128
+
129
+ function getText(node) {
130
+ var txt;
131
+
132
+ if (node.nodeType === 3) {
133
+ return node.data;
134
+ }
135
+
136
+ if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
137
+ return '';
138
+ }
139
+
140
+ txt = '';
141
+
142
+ if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
143
+ txt += '\n';
144
+ }
145
+
146
+ if ((node = node.firstChild)) {
147
+ do {
148
+ txt += getText(node);
149
+ } while ((node = node.nextSibling));
150
+ }
151
+
152
+ return txt;
153
+ }
154
+
155
+ function stepThroughMatches(node, matches, replaceFn) {
156
+ var startNode, endNode, startNodeIndex,
157
+ endNodeIndex, innerNodes = [], atIndex = 0, curNode = node,
158
+ matchLocation, matchIndex = 0;
159
+
160
+ matches = matches.slice(0);
161
+ matches.sort(function(a, b) {
162
+ return a.start - b.start;
163
+ });
164
+
165
+ matchLocation = matches.shift();
166
+
167
+ out: while (true) {
168
+ if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) {
169
+ atIndex++;
170
+ }
171
+
172
+ if (curNode.nodeType === 3) {
173
+ if (!endNode && curNode.length + atIndex >= matchLocation.end) {
174
+ // We've found the ending
175
+ endNode = curNode;
176
+ endNodeIndex = matchLocation.end - atIndex;
177
+ } else if (startNode) {
178
+ // Intersecting node
179
+ innerNodes.push(curNode);
180
+ }
181
+
182
+ if (!startNode && curNode.length + atIndex > matchLocation.start) {
183
+ // We've found the match start
184
+ startNode = curNode;
185
+ startNodeIndex = matchLocation.start - atIndex;
186
+ }
187
+
188
+ atIndex += curNode.length;
189
+ }
190
+
191
+ if (startNode && endNode) {
192
+ curNode = replaceFn({
193
+ startNode: startNode,
194
+ startNodeIndex: startNodeIndex,
195
+ endNode: endNode,
196
+ endNodeIndex: endNodeIndex,
197
+ innerNodes: innerNodes,
198
+ match: matchLocation.text,
199
+ matchIndex: matchIndex
200
+ });
201
+
202
+ // replaceFn has to return the node that replaced the endNode
203
+ // and then we step back so we can continue from the end of the
204
+ // match:
205
+ atIndex -= (endNode.length - endNodeIndex);
206
+ startNode = null;
207
+ endNode = null;
208
+ innerNodes = [];
209
+ matchLocation = matches.shift();
210
+ matchIndex++;
211
+
212
+ if (!matchLocation) {
213
+ break; // no more matches
214
+ }
215
+ } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
216
+ // Move down
217
+ curNode = curNode.firstChild;
218
+ continue;
219
+ } else if (curNode.nextSibling) {
220
+ // Move forward:
221
+ curNode = curNode.nextSibling;
222
+ continue;
223
+ }
224
+
225
+ // Move forward or up:
226
+ while (true) {
227
+ if (curNode.nextSibling) {
228
+ curNode = curNode.nextSibling;
229
+ break;
230
+ } else if (curNode.parentNode !== node) {
231
+ curNode = curNode.parentNode;
232
+ } else {
233
+ break out;
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Generates the actual replaceFn which splits up text nodes
241
+ * and inserts the replacement element.
242
+ */
243
+ function genReplacer(callback) {
244
+ function makeReplacementNode(fill, matchIndex) {
245
+ var match = matches[matchIndex];
246
+
247
+ if (!match.stencil) {
248
+ match.stencil = callback(match);
249
+ }
250
+
251
+ var clone = match.stencil.cloneNode(false);
252
+ clone.setAttribute('data-mce-index', matchIndex);
253
+
254
+ if (fill) {
255
+ clone.appendChild(dom.doc.createTextNode(fill));
256
+ }
257
+
258
+ return clone;
259
+ }
260
+
261
+ return function(range) {
262
+ var before, after, parentNode, startNode = range.startNode,
263
+ endNode = range.endNode, matchIndex = range.matchIndex,
264
+ doc = dom.doc;
265
+
266
+ if (startNode === endNode) {
267
+ var node = startNode;
268
+
269
+ parentNode = node.parentNode;
270
+ if (range.startNodeIndex > 0) {
271
+ // Add "before" text node (before the match)
272
+ before = doc.createTextNode(node.data.substring(0, range.startNodeIndex));
273
+ parentNode.insertBefore(before, node);
274
+ }
275
+
276
+ // Create the replacement node:
277
+ var el = makeReplacementNode(range.match, matchIndex);
278
+ parentNode.insertBefore(el, node);
279
+ if (range.endNodeIndex < node.length) {
280
+ // Add "after" text node (after the match)
281
+ after = doc.createTextNode(node.data.substring(range.endNodeIndex));
282
+ parentNode.insertBefore(after, node);
283
+ }
284
+
285
+ node.parentNode.removeChild(node);
286
+
287
+ return el;
288
+ } else {
289
+ // Replace startNode -> [innerNodes...] -> endNode (in that order)
290
+ before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex));
291
+ after = doc.createTextNode(endNode.data.substring(range.endNodeIndex));
292
+ var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex);
293
+ var innerEls = [];
294
+
295
+ for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
296
+ var innerNode = range.innerNodes[i];
297
+ var innerEl = makeReplacementNode(innerNode.data, matchIndex);
298
+ innerNode.parentNode.replaceChild(innerEl, innerNode);
299
+ innerEls.push(innerEl);
300
+ }
301
+
302
+ var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
303
+
304
+ parentNode = startNode.parentNode;
305
+ parentNode.insertBefore(before, startNode);
306
+ parentNode.insertBefore(elA, startNode);
307
+ parentNode.removeChild(startNode);
308
+
309
+ parentNode = endNode.parentNode;
310
+ parentNode.insertBefore(elB, endNode);
311
+ parentNode.insertBefore(after, endNode);
312
+ parentNode.removeChild(endNode);
313
+
314
+ return elB;
315
+ }
316
+ };
317
+ }
318
+
319
+ function unwrapElement(element) {
320
+ var parentNode = element.parentNode;
321
+ parentNode.insertBefore(element.firstChild, element);
322
+ element.parentNode.removeChild(element);
323
+ }
324
+
325
+ function getWrappersByIndex(index) {
326
+ var elements = node.getElementsByTagName('*'), wrappers = [];
327
+
328
+ index = typeof(index) == "number" ? "" + index : null;
329
+
330
+ for (var i = 0; i < elements.length; i++) {
331
+ var element = elements[i], dataIndex = element.getAttribute('data-mce-index');
332
+
333
+ if (dataIndex !== null && dataIndex.length) {
334
+ if (dataIndex === index || index === null) {
335
+ wrappers.push(element);
336
+ }
337
+ }
338
+ }
339
+
340
+ return wrappers;
341
+ }
342
+
343
+ /**
344
+ * Returns the index of a specific match object or -1 if it isn't found.
345
+ *
346
+ * @param {Match} match Text match object.
347
+ * @return {Number} Index of match or -1 if it isn't found.
348
+ */
349
+ function indexOf(match) {
350
+ var i = matches.length;
351
+ while (i--) {
352
+ if (matches[i] === match) {
353
+ return i;
354
+ }
355
+ }
356
+
357
+ return -1;
358
+ }
359
+
360
+ /**
361
+ * Filters the matches. If the callback returns true it stays if not it gets removed.
362
+ *
363
+ * @param {Function} callback Callback to execute for each match.
364
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
365
+ */
366
+ function filter(callback) {
367
+ var filteredMatches = [];
368
+
369
+ each(function(match, i) {
370
+ if (callback(match, i)) {
371
+ filteredMatches.push(match);
372
+ }
373
+ });
374
+
375
+ matches = filteredMatches;
376
+
377
+ /*jshint validthis:true*/
378
+ return this;
379
+ }
380
+
381
+ /**
382
+ * Executes the specified callback for each match.
383
+ *
384
+ * @param {Function} callback Callback to execute for each match.
385
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
386
+ */
387
+ function each(callback) {
388
+ for (var i = 0, l = matches.length; i < l; i++) {
389
+ if (callback(matches[i], i) === false) {
390
+ break;
391
+ }
392
+ }
393
+
394
+ /*jshint validthis:true*/
395
+ return this;
396
+ }
397
+
398
+ /**
399
+ * Wraps the current matches with nodes created by the specified callback.
400
+ * Multiple clones of these matches might occur on matches that are on multiple nodex.
401
+ *
402
+ * @param {Function} callback Callback to execute in order to create elements for matches.
403
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
404
+ */
405
+ function wrap(callback) {
406
+ if (matches.length) {
407
+ stepThroughMatches(node, matches, genReplacer(callback));
408
+ }
409
+
410
+ /*jshint validthis:true*/
411
+ return this;
412
+ }
413
+
414
+ /**
415
+ * Finds the specified regexp and adds them to the matches collection.
416
+ *
417
+ * @param {RegExp} regex Global regexp to search the current node by.
418
+ * @param {Object} [data] Optional custom data element for the match.
419
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
420
+ */
421
+ function find(regex, data) {
422
+ if (text && regex.global) {
423
+ while ((m = regex.exec(text))) {
424
+ matches.push(createMatch(m, data));
425
+ }
426
+ }
427
+
428
+ return this;
429
+ }
430
+
431
+ /**
432
+ * Unwraps the specified match object or all matches if unspecified.
433
+ *
434
+ * @param {Object} [match] Optional match object.
435
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
436
+ */
437
+ function unwrap(match) {
438
+ var i, elements = getWrappersByIndex(match ? indexOf(match) : null);
439
+
440
+ i = elements.length;
441
+ while (i--) {
442
+ unwrapElement(elements[i]);
443
+ }
444
+
445
+ return this;
446
+ }
447
+
448
+ /**
449
+ * Returns a match object by the specified DOM element.
450
+ *
451
+ * @param {DOMElement} element Element to return match object for.
452
+ * @return {Object} Match object for the specified element.
453
+ */
454
+ function matchFromElement(element) {
455
+ return matches[element.getAttribute('data-mce-index')];
456
+ }
457
+
458
+ /**
459
+ * Returns a DOM element from the specified match element. This will be the first element if it's split
460
+ * on multiple nodes.
461
+ *
462
+ * @param {Object} match Match element to get first element of.
463
+ * @return {DOMElement} DOM element for the specified match object.
464
+ */
465
+ function elementFromMatch(match) {
466
+ return getWrappersByIndex(indexOf(match))[0];
467
+ }
468
+
469
+ /**
470
+ * Adds match the specified range for example a grammar line.
471
+ *
472
+ * @param {Number} start Start offset.
473
+ * @param {Number} length Length of the text.
474
+ * @param {Object} data Custom data object for match.
475
+ * @return {DomTextMatcher} Current DomTextMatcher instance.
476
+ */
477
+ function add(start, length, data) {
478
+ matches.push({
479
+ start: start,
480
+ end: start + length,
481
+ text: text.substr(start, length),
482
+ data: data
483
+ });
484
+
485
+ return this;
486
+ }
487
+
488
+ /**
489
+ * Returns a DOM range for the specified match.
490
+ *
491
+ * @param {Object} match Match object to get range for.
492
+ * @return {DOMRange} DOM Range for the specified match.
493
+ */
494
+ function rangeFromMatch(match) {
495
+ var wrappers = getWrappersByIndex(indexOf(match));
496
+
497
+ var rng = editor.dom.createRng();
498
+ rng.setStartBefore(wrappers[0]);
499
+ rng.setEndAfter(wrappers[wrappers.length - 1]);
500
+
501
+ return rng;
502
+ }
503
+
504
+ /**
505
+ * Replaces the specified match with the specified text.
506
+ *
507
+ * @param {Object} match Match object to replace.
508
+ * @param {String} text Text to replace the match with.
509
+ * @return {DOMRange} DOM range produced after the replace.
510
+ */
511
+ function replace(match, text) {
512
+ var rng = rangeFromMatch(match);
513
+
514
+ rng.deleteContents();
515
+
516
+ if (text.length > 0) {
517
+ rng.insertNode(editor.dom.doc.createTextNode(text));
518
+ }
519
+
520
+ return rng;
521
+ }
522
+
523
+ /**
524
+ * Resets the DomTextMatcher instance. This will remove any wrapped nodes and remove any matches.
525
+ *
526
+ * @return {[type]} [description]
527
+ */
528
+ function reset() {
529
+ matches.splice(0, matches.length);
530
+ unwrap();
531
+
532
+ return this;
533
+ }
534
+
535
+ text = getText(node);
536
+
537
+ return {
538
+ text: text,
539
+ matches: matches,
540
+ each: each,
541
+ filter: filter,
542
+ reset: reset,
543
+ matchFromElement: matchFromElement,
544
+ elementFromMatch: elementFromMatch,
545
+ find: find,
546
+ add: add,
547
+ wrap: wrap,
548
+ unwrap: unwrap,
549
+ replace: replace,
550
+ rangeFromMatch: rangeFromMatch,
551
+ indexOf: indexOf
552
+ };
553
+ };
554
+ });
555
+
556
+ // Included from: js/tinymce/plugins/spellchecker/classes/Plugin.js
557
+
558
+ /**
559
+ * Plugin.js
560
+ *
561
+ * Copyright, Moxiecode Systems AB
562
+ * Released under LGPL License.
563
+ *
564
+ * License: http://www.tinymce.com/license
565
+ * Contributing: http://www.tinymce.com/contributing
566
+ */
567
+
568
+ /*jshint camelcase:false */
569
+
570
+ /**
571
+ * This class contains all core logic for the spellchecker plugin.
572
+ *
573
+ * @class tinymce.spellcheckerplugin.Plugin
574
+ * @private
575
+ */
576
+ define("tinymce/spellcheckerplugin/Plugin", [
577
+ "tinymce/spellcheckerplugin/DomTextMatcher",
578
+ "tinymce/PluginManager",
579
+ "tinymce/util/Tools",
580
+ "tinymce/ui/Menu",
581
+ "tinymce/dom/DOMUtils",
582
+ "tinymce/util/XHR",
583
+ "tinymce/util/URI",
584
+ "tinymce/util/JSON"
585
+ ], function(DomTextMatcher, PluginManager, Tools, Menu, DOMUtils, XHR, URI, JSON) {
586
+ PluginManager.add('spellchecker', function(editor, url) {
587
+ var languageMenuItems, self = this, lastSuggestions, started, suggestionsMenu, settings = editor.settings;
588
+ var hasDictionarySupport;
589
+
590
+ function getTextMatcher() {
591
+ if (!self.textMatcher) {
592
+ self.textMatcher = new DomTextMatcher(editor.getBody(), editor);
593
+ }
594
+
595
+ return self.textMatcher;
596
+ }
597
+
598
+ function buildMenuItems(listName, languageValues) {
599
+ var items = [];
600
+
601
+ Tools.each(languageValues, function(languageValue) {
602
+ items.push({
603
+ selectable: true,
604
+ text: languageValue.name,
605
+ data: languageValue.value
606
+ });
607
+ });
608
+
609
+ return items;
610
+ }
611
+
612
+ var languagesString = settings.spellchecker_languages ||
613
+ 'English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr_FR,' +
614
+ 'German=de,Italian=it,Polish=pl,Portuguese=pt_BR,' +
615
+ 'Spanish=es,Swedish=sv';
616
+
617
+ languageMenuItems = buildMenuItems('Language',
618
+ Tools.map(languagesString.split(','), function(langPair) {
619
+ langPair = langPair.split('=');
620
+
621
+ return {
622
+ name: langPair[0],
623
+ value: langPair[1]
624
+ };
625
+ })
626
+ );
627
+
628
+ function isEmpty(obj) {
629
+ /*jshint unused:false*/
630
+ /*eslint no-unused-vars:0 */
631
+ for (var name in obj) {
632
+ return false;
633
+ }
634
+
635
+ return true;
636
+ }
637
+
638
+ function showSuggestions(word, spans) {
639
+ var items = [], suggestions = lastSuggestions[word];
640
+
641
+ Tools.each(suggestions, function(suggestion) {
642
+ items.push({
643
+ text: suggestion,
644
+ onclick: function() {
645
+ editor.insertContent(editor.dom.encode(suggestion));
646
+ editor.dom.remove(spans);
647
+ checkIfFinished();
648
+ }
649
+ });
650
+ });
651
+
652
+ items.push({text: '-'});
653
+
654
+ if (hasDictionarySupport) {
655
+ items.push({text: 'Add to Dictionary', onclick: function() {
656
+ addToDictionary(word, spans);
657
+ }});
658
+ }
659
+
660
+ items.push.apply(items, [
661
+ {text: 'Ignore', onclick: function() {
662
+ ignoreWord(word, spans);
663
+ }},
664
+
665
+ {text: 'Ignore all', onclick: function() {
666
+ ignoreWord(word, spans, true);
667
+ }}
668
+ ]);
669
+
670
+ // Render menu
671
+ suggestionsMenu = new Menu({
672
+ items: items,
673
+ context: 'contextmenu',
674
+ onautohide: function(e) {
675
+ if (e.target.className.indexOf('spellchecker') != -1) {
676
+ e.preventDefault();
677
+ }
678
+ },
679
+ onhide: function() {
680
+ suggestionsMenu.remove();
681
+ suggestionsMenu = null;
682
+ }
683
+ });
684
+
685
+ suggestionsMenu.renderTo(document.body);
686
+
687
+ // Position menu
688
+ var pos = DOMUtils.DOM.getPos(editor.getContentAreaContainer());
689
+ var targetPos = editor.dom.getPos(spans[0]);
690
+ var root = editor.dom.getRoot();
691
+
692
+ // Adjust targetPos for scrolling in the editor
693
+ if (root.nodeName == 'BODY') {
694
+ targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
695
+ targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
696
+ } else {
697
+ targetPos.x -= root.scrollLeft;
698
+ targetPos.y -= root.scrollTop;
699
+ }
700
+
701
+ pos.x += targetPos.x;
702
+ pos.y += targetPos.y;
703
+
704
+ suggestionsMenu.moveTo(pos.x, pos.y + spans[0].offsetHeight);
705
+ }
706
+
707
+ function getWordCharPattern() {
708
+ // Regexp for finding word specific characters this will split words by
709
+ // spaces, quotes, copy right characters etc. It's escaped with unicode characters
710
+ // to make it easier to output scripts on servers using different encodings
711
+ // so if you add any characters outside the 128 byte range make sure to escape it
712
+ return editor.getParam('spellchecker_wordchar_pattern') || new RegExp("[^" +
713
+ "\\s!\"#$%&()*+,-./:;<=>?@[\\]^_{|}`" +
714
+ "\u00a7\u00a9\u00ab\u00ae\u00b1\u00b6\u00b7\u00b8\u00bb" +
715
+ "\u00bc\u00bd\u00be\u00bf\u00d7\u00f7\u00a4\u201d\u201c\u201e\u00a0\u2002\u2003\u2009" +
716
+ "]+", "g");
717
+ }
718
+
719
+ function defaultSpellcheckCallback(method, text, doneCallback, errorCallback) {
720
+ var data = {method: method}, postData = '';
721
+
722
+ if (method == "spellcheck") {
723
+ data.text = text;
724
+ data.lang = settings.spellchecker_language;
725
+ }
726
+
727
+ if (method == "addToDictionary") {
728
+ data.word = text;
729
+ }
730
+
731
+ Tools.each(data, function(value, key) {
732
+ if (postData) {
733
+ postData += '&';
734
+ }
735
+
736
+ postData += key + '=' + encodeURIComponent(value);
737
+ });
738
+
739
+ XHR.send({
740
+ url: new URI(url).toAbsolute(settings.spellchecker_rpc_url),
741
+ type: "post",
742
+ content_type: 'application/x-www-form-urlencoded',
743
+ data: postData,
744
+ success: function(result) {
745
+ result = JSON.parse(result);
746
+
747
+ if (!result) {
748
+ errorCallback("Sever response wasn't proper JSON.");
749
+ } else if (result.error) {
750
+ errorCallback(result.error);
751
+ } else {
752
+ doneCallback(result);
753
+ }
754
+ },
755
+ error: function(type, xhr) {
756
+ errorCallback("Spellchecker request error: " + xhr.status);
757
+ }
758
+ });
759
+ }
760
+
761
+ function sendRpcCall(name, data, successCallback, errorCallback) {
762
+ var spellCheckCallback = settings.spellchecker_callback || defaultSpellcheckCallback;
763
+ spellCheckCallback.call(self, name, data, successCallback, errorCallback);
764
+ }
765
+
766
+ function spellcheck() {
767
+ if (started) {
768
+ finish();
769
+ return;
770
+ } else {
771
+ finish();
772
+ }
773
+
774
+ function errorCallback(message) {
775
+ editor.windowManager.alert(message);
776
+ editor.setProgressState(false);
777
+ finish();
778
+ }
779
+
780
+ editor.setProgressState(true);
781
+ sendRpcCall("spellcheck", getTextMatcher().text, markErrors, errorCallback);
782
+ editor.focus();
783
+ }
784
+
785
+ function checkIfFinished() {
786
+ if (!editor.dom.select('span.mce-spellchecker-word').length) {
787
+ finish();
788
+ }
789
+ }
790
+
791
+ function addToDictionary(word, spans) {
792
+ editor.setProgressState(true);
793
+
794
+ sendRpcCall("addToDictionary", word, function() {
795
+ editor.setProgressState(false);
796
+ editor.dom.remove(spans, true);
797
+ checkIfFinished();
798
+ }, function(message) {
799
+ editor.windowManager.alert(message);
800
+ editor.setProgressState(false);
801
+ });
802
+ }
803
+
804
+ function ignoreWord(word, spans, all) {
805
+ editor.selection.collapse();
806
+
807
+ if (all) {
808
+ Tools.each(editor.dom.select('span.mce-spellchecker-word'), function(span) {
809
+ if (span.getAttribute('data-mce-word') == word) {
810
+ editor.dom.remove(span, true);
811
+ }
812
+ });
813
+ } else {
814
+ editor.dom.remove(spans, true);
815
+ }
816
+
817
+ checkIfFinished();
818
+ }
819
+
820
+ function finish() {
821
+ getTextMatcher().reset();
822
+ self.textMatcher = null;
823
+
824
+ if (started) {
825
+ started = false;
826
+ editor.fire('SpellcheckEnd');
827
+ }
828
+ }
829
+
830
+ function getElmIndex(elm) {
831
+ var value = elm.getAttribute('data-mce-index');
832
+
833
+ if (typeof(value) == "number") {
834
+ return "" + value;
835
+ }
836
+
837
+ return value;
838
+ }
839
+
840
+ function findSpansByIndex(index) {
841
+ var nodes, spans = [];
842
+
843
+ nodes = Tools.toArray(editor.getBody().getElementsByTagName('span'));
844
+ if (nodes.length) {
845
+ for (var i = 0; i < nodes.length; i++) {
846
+ var nodeIndex = getElmIndex(nodes[i]);
847
+
848
+ if (nodeIndex === null || !nodeIndex.length) {
849
+ continue;
850
+ }
851
+
852
+ if (nodeIndex === index.toString()) {
853
+ spans.push(nodes[i]);
854
+ }
855
+ }
856
+ }
857
+
858
+ return spans;
859
+ }
860
+
861
+ editor.on('click', function(e) {
862
+ var target = e.target;
863
+
864
+ if (target.className == "mce-spellchecker-word") {
865
+ e.preventDefault();
866
+
867
+ var spans = findSpansByIndex(getElmIndex(target));
868
+
869
+ if (spans.length > 0) {
870
+ var rng = editor.dom.createRng();
871
+ rng.setStartBefore(spans[0]);
872
+ rng.setEndAfter(spans[spans.length - 1]);
873
+ editor.selection.setRng(rng);
874
+ showSuggestions(target.getAttribute('data-mce-word'), spans);
875
+ }
876
+ }
877
+ });
878
+
879
+ editor.addMenuItem('spellchecker', {
880
+ text: 'Spellcheck',
881
+ context: 'tools',
882
+ onclick: spellcheck,
883
+ selectable: true,
884
+ onPostRender: function() {
885
+ var self = this;
886
+
887
+ self.active(started);
888
+
889
+ editor.on('SpellcheckStart SpellcheckEnd', function() {
890
+ self.active(started);
891
+ });
892
+ }
893
+ });
894
+
895
+ function updateSelection(e) {
896
+ var selectedLanguage = settings.spellchecker_language;
897
+
898
+ e.control.items().each(function(ctrl) {
899
+ ctrl.active(ctrl.settings.data === selectedLanguage);
900
+ });
901
+ }
902
+
903
+ /**
904
+ * Find the specified words and marks them. It will also show suggestions for those words.
905
+ *
906
+ * @example
907
+ * editor.plugins.spellchecker.markErrors({
908
+ * dictionary: true,
909
+ * words: {
910
+ * "word1": ["suggestion 1", "Suggestion 2"]
911
+ * }
912
+ * });
913
+ * @param {Object} data Data object containing the words with suggestions.
914
+ */
915
+ function markErrors(data) {
916
+ var suggestions;
917
+
918
+ if (data.words) {
919
+ hasDictionarySupport = !!data.dictionary;
920
+ suggestions = data.words;
921
+ } else {
922
+ // Fallback to old format
923
+ suggestions = data;
924
+ }
925
+
926
+ editor.setProgressState(false);
927
+
928
+ if (isEmpty(suggestions)) {
929
+ editor.windowManager.alert('No misspellings found');
930
+ started = false;
931
+ return;
932
+ }
933
+
934
+ lastSuggestions = suggestions;
935
+
936
+ getTextMatcher().find(getWordCharPattern()).filter(function(match) {
937
+ return !!suggestions[match.text];
938
+ }).wrap(function(match) {
939
+ return editor.dom.create('span', {
940
+ "class": 'mce-spellchecker-word',
941
+ "data-mce-bogus": 1,
942
+ "data-mce-word": match.text
943
+ });
944
+ });
945
+
946
+ started = true;
947
+ editor.fire('SpellcheckStart');
948
+ }
949
+
950
+ var buttonArgs = {
951
+ tooltip: 'Spellcheck',
952
+ onclick: spellcheck,
953
+ onPostRender: function() {
954
+ var self = this;
955
+
956
+ editor.on('SpellcheckStart SpellcheckEnd', function() {
957
+ self.active(started);
958
+ });
959
+ }
960
+ };
961
+
962
+ if (languageMenuItems.length > 1) {
963
+ buttonArgs.type = 'splitbutton';
964
+ buttonArgs.menu = languageMenuItems;
965
+ buttonArgs.onshow = updateSelection;
966
+ buttonArgs.onselect = function(e) {
967
+ settings.spellchecker_language = e.control.settings.data;
968
+ };
969
+ }
970
+
971
+ editor.addButton('spellchecker', buttonArgs);
972
+ editor.addCommand('mceSpellCheck', spellcheck);
973
+
974
+ editor.on('remove', function() {
975
+ if (suggestionsMenu) {
976
+ suggestionsMenu.remove();
977
+ suggestionsMenu = null;
978
+ }
979
+ });
980
+
981
+ editor.on('change', checkIfFinished);
982
+
983
+ this.getTextMatcher = getTextMatcher;
984
+ this.getWordCharPattern = getWordCharPattern;
985
+ this.markErrors = markErrors;
986
+ this.getLanguage = function() {
987
+ return settings.spellchecker_language;
988
+ };
989
+
990
+ // Set default spellchecker language if it's not specified
991
+ settings.spellchecker_language = settings.spellchecker_language || settings.language || 'en';
992
+ });
993
+ });
994
+
995
+ expose(["tinymce/spellcheckerplugin/DomTextMatcher"]);
996
+ })(this);