plugin.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /**
  2. * @license Copyright (c) CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.html or http://ckeditor.com/license
  4. */
  5. CKEDITOR.plugins.add("wordcount", {
  6. lang: "ar,ca,da,de,el,en,es,fi,fr,he,hr,it,jp,nl,no,pl,pt,pt-br,ru,sv,tr,zh-cn", // %REMOVE_LINE_CORE%
  7. version: 1.15,
  8. requires: 'htmlwriter,notification,undo',
  9. init: function (editor) {
  10. var defaultFormat = "",
  11. intervalId,
  12. lastWordCount = -1,
  13. lastCharCount = -1,
  14. limitReachedNotified = false,
  15. limitRestoredNotified = false,
  16. snapShot = editor.getSnapshot();
  17. var dispatchEvent = function (type, currentLength, maxLength) {
  18. if (typeof document.dispatchEvent == 'undefined') {
  19. return;
  20. }
  21. type = 'ckeditor.wordcount.' + type;
  22. var cEvent;
  23. var eventInitDict = {
  24. bubbles: false,
  25. cancelable: true,
  26. detail: {
  27. currentLength: currentLength,
  28. maxLength: maxLength
  29. }
  30. };
  31. try {
  32. cEvent = new CustomEvent(type, eventInitDict);
  33. } catch (o_O) {
  34. cEvent = document.createEvent('CustomEvent');
  35. cEvent.initCustomEvent(
  36. type,
  37. eventInitDict.bubbles,
  38. eventInitDict.cancelable,
  39. eventInitDict.detail
  40. );
  41. }
  42. document.dispatchEvent(cEvent);
  43. };
  44. // Default Config
  45. var defaultConfig = {
  46. showParagraphs: true,
  47. showWordCount: true,
  48. showCharCount: false,
  49. countSpacesAsChars: false,
  50. countHTML: false,
  51. hardLimit: true,
  52. //MAXLENGTH Properties
  53. maxWordCount: -1,
  54. maxCharCount: -1,
  55. // Filter
  56. filter: null,
  57. //DisAllowed functions
  58. wordCountGreaterThanMaxLengthEvent: function (currentLength, maxLength) {
  59. dispatchEvent('wordCountGreaterThanMaxLengthEvent', currentLength, maxLength);
  60. },
  61. charCountGreaterThanMaxLengthEvent: function (currentLength, maxLength) {
  62. dispatchEvent('charCountGreaterThanMaxLengthEvent', currentLength, maxLength);
  63. },
  64. //Allowed Functions
  65. wordCountLessThanMaxLengthEvent: function (currentLength, maxLength) {
  66. dispatchEvent('wordCountLessThanMaxLengthEvent', currentLength, maxLength);
  67. },
  68. charCountLessThanMaxLengthEvent: function (currentLength, maxLength) {
  69. dispatchEvent('charCountLessThanMaxLengthEvent', currentLength, maxLength);
  70. }
  71. };
  72. // Get Config & Lang
  73. var config = CKEDITOR.tools.extend(defaultConfig, editor.config.wordcount || {}, true);
  74. if (config.showParagraphs) {
  75. defaultFormat += editor.lang.wordcount.Paragraphs + " %paragraphs%";
  76. }
  77. if (config.showParagraphs && (config.showWordCount || config.showCharCount)) {
  78. defaultFormat += ", ";
  79. }
  80. if (config.showWordCount) {
  81. defaultFormat += editor.lang.wordcount.WordCount + " %wordCount%";
  82. if (config.maxWordCount > -1) {
  83. defaultFormat += "/" + config.maxWordCount;
  84. }
  85. }
  86. if (config.showCharCount && config.showWordCount) {
  87. defaultFormat += ", ";
  88. }
  89. if (config.showCharCount) {
  90. var charLabel = editor.lang.wordcount[config.countHTML ? "CharCountWithHTML" : "CharCount"];
  91. defaultFormat += charLabel + " %charCount%";
  92. if (config.maxCharCount > -1) {
  93. defaultFormat += "/" + config.maxCharCount;
  94. }
  95. }
  96. var format = defaultFormat;
  97. if (config.loadCss === undefined || config.loadCss) {
  98. CKEDITOR.document.appendStyleSheet(this.path + "css/wordcount.css");
  99. }
  100. function counterId(editorInstance) {
  101. return "cke_wordcount_" + editorInstance.name;
  102. }
  103. function counterElement(editorInstance) {
  104. return document.getElementById(counterId(editorInstance));
  105. }
  106. function strip(html) {
  107. var tmp = document.createElement("div");
  108. // Add filter before strip
  109. html = filter(html);
  110. tmp.innerHTML = html;
  111. if (tmp.textContent == "" && typeof tmp.innerText == "undefined") {
  112. return "";
  113. }
  114. return tmp.textContent || tmp.innerText;
  115. }
  116. /**
  117. * Implement filter to add or remove before counting
  118. * @param html
  119. * @returns string
  120. */
  121. function filter(html) {
  122. if(config.filter instanceof CKEDITOR.htmlParser.filter) {
  123. var fragment = CKEDITOR.htmlParser.fragment.fromHtml(html),
  124. writer = new CKEDITOR.htmlParser.basicWriter();
  125. config.filter.applyTo( fragment );
  126. fragment.writeHtml( writer );
  127. return writer.getHtml();
  128. }
  129. return html;
  130. }
  131. function countCharacters(text, editorInstance) {
  132. if (config.countHTML) {
  133. return (filter(text).length);
  134. } else {
  135. var normalizedText;
  136. // strip body tags
  137. if (editor.config.fullPage) {
  138. var i = text.search(new RegExp("<body>", "i"));
  139. if (i != -1) {
  140. var j = text.search(new RegExp("</body>", "i"));
  141. text = text.substring(i + 6, j);
  142. }
  143. }
  144. normalizedText = text;
  145. if (!config.countSpacesAsChars) {
  146. normalizedText = text.
  147. replace(/\s/g, "").
  148. replace(/&nbsp;/g, "");
  149. }
  150. normalizedText = normalizedText.
  151. replace(/(\r\n|\n|\r)/gm, "").
  152. replace(/&nbsp;/gi, " ");
  153. normalizedText = strip(normalizedText).replace(/^([\t\r\n]*)$/, "");
  154. return(normalizedText.length);
  155. }
  156. }
  157. function countParagraphs(text) {
  158. return (text.replace(/&nbsp;/g, " ").replace(/(<([^>]+)>)/ig, "").replace(/^\s*$[\n\r]{1,}/gm, "++").split("++").length);
  159. }
  160. function countWords(text) {
  161. var normalizedText = text.
  162. replace(/(\r\n|\n|\r)/gm, " ").
  163. replace(/^\s+|\s+$/g, "").
  164. replace("&nbsp;", " ");
  165. normalizedText = strip(normalizedText);
  166. var words = normalizedText.split(/\s+/);
  167. for (var wordIndex = words.length - 1; wordIndex >= 0; wordIndex--) {
  168. if (words[wordIndex].match(/^([\s\t\r\n]*)$/)) {
  169. words.splice(wordIndex, 1);
  170. }
  171. }
  172. return (words.length);
  173. }
  174. function limitReached(editorInstance, notify) {
  175. limitReachedNotified = true;
  176. limitRestoredNotified = false;
  177. if (config.hardLimit) {
  178. editorInstance.loadSnapshot(snapShot);
  179. // lock editor
  180. editorInstance.config.Locked = 1;
  181. }
  182. if (!notify) {
  183. counterElement(editorInstance).className = "cke_path_item cke_wordcountLimitReached";
  184. editorInstance.fire("limitReached", {}, editor);
  185. }
  186. }
  187. function limitRestored(editorInstance) {
  188. limitRestoredNotified = true;
  189. limitReachedNotified = false;
  190. editorInstance.config.Locked = 0;
  191. snapShot = editor.getSnapshot();
  192. counterElement(editorInstance).className = "cke_path_item";
  193. }
  194. function updateCounter(editorInstance) {
  195. var paragraphs = 0,
  196. wordCount = 0,
  197. charCount = 0,
  198. text;
  199. if (text = editorInstance.getData()) {
  200. if (config.showCharCount) {
  201. charCount = countCharacters(text, editorInstance);
  202. }
  203. if (config.showParagraphs) {
  204. paragraphs = countParagraphs(text);
  205. }
  206. if (config.showWordCount) {
  207. wordCount = countWords(text);
  208. }
  209. }
  210. var html = format.replace("%wordCount%", wordCount).replace("%charCount%", charCount).replace("%paragraphs%", paragraphs);
  211. (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).wordCount = wordCount;
  212. (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).charCount = charCount;
  213. if (CKEDITOR.env.gecko) {
  214. counterElement(editorInstance).innerHTML = html;
  215. } else {
  216. counterElement(editorInstance).innerText = html;
  217. }
  218. if (charCount == lastCharCount && wordCount == lastWordCount) {
  219. return true;
  220. }
  221. //If the limit is already over, allow the deletion of characters/words. Otherwise,
  222. //the user would have to delete at one go the number of offending characters
  223. var deltaWord = wordCount - lastWordCount;
  224. var deltaChar = charCount - lastCharCount;
  225. lastWordCount = wordCount;
  226. lastCharCount = charCount;
  227. if (lastWordCount == -1) {
  228. lastWordCount = wordCount;
  229. }
  230. if (lastCharCount == -1) {
  231. lastCharCount = charCount;
  232. }
  233. // Check for word limit and/or char limit
  234. if ((config.maxWordCount > -1 && wordCount > config.maxWordCount && deltaWord > 0) ||
  235. (config.maxCharCount > -1 && charCount > config.maxCharCount && deltaChar > 0)) {
  236. limitReached(editorInstance, limitReachedNotified);
  237. } else if ((config.maxWordCount == -1 || wordCount < config.maxWordCount) &&
  238. (config.maxCharCount == -1 || charCount < config.maxCharCount)) {
  239. limitRestored(editorInstance);
  240. } else {
  241. snapShot = editorInstance.getSnapshot();
  242. }
  243. // Fire Custom Events
  244. if (config.charCountGreaterThanMaxLengthEvent && config.charCountLessThanMaxLengthEvent) {
  245. if (charCount > config.maxCharCount && config.maxCharCount > -1) {
  246. config.charCountGreaterThanMaxLengthEvent(charCount, config.maxCharCount);
  247. } else {
  248. config.charCountLessThanMaxLengthEvent(charCount, config.maxCharCount);
  249. }
  250. }
  251. if (config.wordCountGreaterThanMaxLengthEvent && config.wordCountLessThanMaxLengthEvent) {
  252. if (wordCount > config.maxWordCount && config.maxWordCount > -1) {
  253. config.wordCountGreaterThanMaxLengthEvent(wordCount, config.maxWordCount);
  254. } else {
  255. config.wordCountLessThanMaxLengthEvent(wordCount, config.maxWordCount);
  256. }
  257. }
  258. return true;
  259. }
  260. editor.on("key", function (event) {
  261. if (editor.mode === "source") {
  262. updateCounter(event.editor);
  263. }
  264. }, editor, null, 100);
  265. editor.on("change", function (event) {
  266. updateCounter(event.editor);
  267. }, editor, null, 100);
  268. editor.on("uiSpace", function (event) {
  269. if (editor.elementMode === CKEDITOR.ELEMENT_MODE_INLINE) {
  270. if (event.data.space == "top") {
  271. event.data.html += "<div class=\"cke_wordcount\" style=\"\"" +
  272. " title=\"" +
  273. editor.lang.wordcount.title +
  274. "\"" +
  275. "><span id=\"" +
  276. counterId(event.editor) +
  277. "\" class=\"cke_path_item\">&nbsp;</span></div>";
  278. }
  279. } else {
  280. if (event.data.space == "bottom") {
  281. event.data.html += "<div class=\"cke_wordcount\" style=\"\"" +
  282. " title=\"" +
  283. editor.lang.wordcount.title +
  284. "\"" +
  285. "><span id=\"" +
  286. counterId(event.editor) +
  287. "\" class=\"cke_path_item\">&nbsp;</span></div>";
  288. }
  289. }
  290. }, editor, null, 100);
  291. editor.on("dataReady", function (event) {
  292. updateCounter(event.editor);
  293. }, editor, null, 100);
  294. editor.on("paste", function(event) {
  295. if (config.maxWordCount > 0 || config.maxCharCount > 0) {
  296. // Check if pasted content is above the limits
  297. var wordCount = -1,
  298. charCount = -1,
  299. text = event.editor.getData() + event.data.dataValue;
  300. if (config.showCharCount) {
  301. charCount = countCharacters(text, event.editor);
  302. }
  303. if (config.showWordCount) {
  304. wordCount = countWords(text);
  305. }
  306. var notification = new CKEDITOR.plugins.notification(event.editor, { message: event.editor.lang.wordcount.pasteWarning, type: 'warning' });
  307. if (config.maxCharCount > 0 && charCount > config.maxCharCount && config.hardLimit) {
  308. notification.show();
  309. event.cancel();
  310. }
  311. if (config.maxWordCount > 0 && wordCount > config.maxWordCount && config.hardLimit) {
  312. notification.show();
  313. event.cancel();
  314. }
  315. }
  316. }, editor, null, 100);
  317. editor.on("afterPaste", function (event) {
  318. updateCounter(event.editor);
  319. }, editor, null, 100);
  320. editor.on("blur", function () {
  321. if (intervalId) {
  322. window.clearInterval(intervalId);
  323. }
  324. }, editor, null, 300);
  325. }
  326. });