/** * @license Copyright (c) CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add("wordcount", { lang: "ar,bg,ca,cs,da,de,el,en,es,eu,fa,fi,fr,he,hr,hu,it,ko,ja,nl,no,pl,pt,pt-br,ru,sk,sv,tr,uk,zh-cn,zh,ro", // %REMOVE_LINE_CORE% version: "1.17.6", requires: 'htmlwriter,notification,undo', bbcodePluginLoaded: false, onLoad: function() { CKEDITOR.document.appendStyleSheet(this.path + "css/wordcount.css"); }, init: function(editor) { var defaultFormat = "", lastWordCount = -1, lastCharCount = -1, lastParagraphs = -1, timeoutId = 0, notification = null; // Default Config var defaultConfig = { showRemaining: false, showParagraphs: true, showWordCount: true, showCharCount: false, countBytesAsChars: false, countSpacesAsChars: false, countHTML: false, countLineBreaks: false, hardLimit: true, warnOnLimitOnly: false, //MAXLENGTH Properties maxWordCount: -1, maxCharCount: -1, maxParagraphs: -1, // Filter filter: null, }; // Get Config & Lang var config = CKEDITOR.tools.extend(defaultConfig, editor.config.wordcount || {}, true); if (config.showParagraphs) { if (config.maxParagraphs > -1) { if (config.showRemaining) { defaultFormat += "%paragraphsCount% " + editor.lang.wordcount.ParagraphsRemaining; } else { defaultFormat += editor.lang.wordcount.Paragraphs + " %paragraphsCount%"; defaultFormat += "/" + config.maxParagraphs; } } else { defaultFormat += editor.lang.wordcount.Paragraphs + " %paragraphsCount%"; } } if (config.showParagraphs && (config.showWordCount || config.showCharCount)) { defaultFormat += ", "; } if (config.showWordCount) { if (config.maxWordCount > -1) { if (config.showRemaining) { defaultFormat += "%wordCount% " + editor.lang.wordcount.WordCountRemaining; } else { defaultFormat += editor.lang.wordcount.WordCount + " %wordCount%"; defaultFormat += "/" + config.maxWordCount; } } else { defaultFormat += editor.lang.wordcount.WordCount + " %wordCount%"; } } if (config.showCharCount && config.showWordCount) { defaultFormat += ", "; } if (config.showCharCount) { if (config.maxCharCount > -1) { if (config.showRemaining) { defaultFormat += "%charCount% " + editor.lang.wordcount[config.countHTML ? "CharCountWithHTMLRemaining" : "CharCountRemaining"]; } else { defaultFormat += editor.lang.wordcount[config.countHTML ? "CharCountWithHTML" : "CharCount"] + " %charCount%"; defaultFormat += "/" + config.maxCharCount; } } else { defaultFormat += editor.lang.wordcount[config.countHTML ? "CharCountWithHTML" : "CharCount"] + " %charCount%"; } } var format = defaultFormat; bbcodePluginLoaded = typeof editor.plugins.bbcode != 'undefined'; function strip(html) { if (bbcodePluginLoaded) { // stripping out BBCode tags [...][/...] return html.replace(/\[.*?\]/gi, ''); } var tmp = document.createElement("div"); // Add filter before strip html = filter(html); tmp.innerHTML = html; if (tmp.textContent == "" && typeof tmp.innerText == "undefined") { return ""; } return tmp.textContent || tmp.innerText; } /** * Implement filter to add or remove before counting * @param html * @returns string */ function filter(html) { if (config.filter instanceof CKEDITOR.htmlParser.filter) { var fragment = CKEDITOR.htmlParser.fragment.fromHtml(html), writer = new CKEDITOR.htmlParser.basicWriter(); config.filter.applyTo(fragment); fragment.writeHtml(writer); return writer.getHtml(); } return html; } function countCharacters(text) { if (config.countHTML) { return config.countBytesAsChars ? countBytes(filter(text)) : filter(text).length; } var normalizedText; // strip body tags if (editor.config.fullPage) { var i = text.search(new RegExp("", "i")); if (i != -1) { var j = text.search(new RegExp("", "i")); text = text.substring(i + 6, j); } } normalizedText = text; if (!config.countSpacesAsChars) { normalizedText = text.replace(/\s/g, "").replace(/ /g, ""); } if (config.countLineBreaks) { normalizedText = normalizedText.replace(/(\r\n|\n|\r)/gm, " "); } else { normalizedText = normalizedText.replace(/(\r\n|\n|\r)/gm, "").replace(/ /gi, " "); } normalizedText = strip(normalizedText).replace(/^([\t\r\n]*)$/, ""); return config.countBytesAsChars ? countBytes(normalizedText) : normalizedText.length; } function countBytes(text) { var count = 0, stringLength = text.length, i; text = String(text || ""); for (i = 0; i < stringLength; i++) { var partCount = encodeURI(text[i]).split("%").length; count += partCount == 1 ? 1 : partCount - 1; } return count; } function countParagraphs(text) { return (text.replace(/ /g, " ").replace(/(<([^>]+)>)/ig, "").replace(/^\s*$[\n\r]{1,}/gm, "++") .split("++").length); } function countWords(text) { var normalizedText = text.replace(/(\r\n|\n|\r)/gm, " ").replace(/^\s+|\s+$/g, "") .replace(" ", " "); normalizedText = strip(normalizedText); var words = normalizedText.split(/\s+/); for (var wordIndex = words.length - 1; wordIndex >= 0; wordIndex--) { if (words[wordIndex].match(/^([\s\t\r\n]*)$/)) { words.splice(wordIndex, 1); } } return (words.length); } function updateCounter(editorInstance) { var paragraphs = 0, wordCount = 0, charCount = 0, text; // BeforeGetData and getData events are fired when calling // getData(). We can prevent this by passing true as an // argument to getData(). This allows us to fire the events // manually with additional event data: firedBy. This additional // data helps differentiate calls to getData() made by // wordCount plugin from calls made by other plugins/code. editorInstance.fire("beforeGetData", { firedBy: "wordCount.updateCounter" }, editor); text = editorInstance.getData(true); editorInstance.fire("getData", { dataValue: text, firedBy: "wordCount.updateCounter" }, editor); if (text) { if (config.showCharCount) { charCount = countCharacters(text); } if (config.showParagraphs) { paragraphs = countParagraphs(text); } if (config.showWordCount) { wordCount = countWords(text); } } var html = format; if (config.showRemaining) { if (config.maxCharCount >= 0) { html = html.replace("%charCount%", config.maxCharCount - charCount); } else { html = html.replace("%charCount%", charCount); } if (config.maxWordCount >= 0) { html = html.replace("%wordCount%", config.maxWordCount - wordCount); } else { html = html.replace("%wordCount%", wordCount); } if (config.maxParagraphs >= 0) { html = html.replace("%paragraphsCount%", config.maxParagraphs - paragraphs); } else { html = html.replace("%paragraphsCount%", paragraphs); } } else { html = html.replace("%wordCount%", wordCount).replace("%charCount%", charCount).replace("%paragraphsCount%", paragraphs); } (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).wordCount = wordCount; (editorInstance.config.wordcount || (editorInstance.config.wordcount = {})).charCount = charCount; if (charCount == lastCharCount && wordCount == lastWordCount && paragraphs == lastParagraphs) { if (charCount == config.maxCharCount || wordCount == config.maxWordCount || paragraphs > config.maxParagraphs) { editorInstance.fire('saveSnapshot'); } return true; } //If the limit is already over, allow the deletion of characters/words. Otherwise, //the user would have to delete at one go the number of offending characters var deltaWord = wordCount - lastWordCount; var deltaChar = charCount - lastCharCount; var deltaParagraphs = paragraphs - lastParagraphs; lastWordCount = wordCount; lastCharCount = charCount; lastParagraphs = paragraphs; if (lastWordCount == -1) { lastWordCount = wordCount; } if (lastCharCount == -1) { lastCharCount = charCount; } if (lastParagraphs == -1) { lastParagraphs = paragraphs; } // update instance editorInstance.wordCount = { paragraphs: paragraphs, wordCount: wordCount, charCount: charCount }; editor.fire('cp-wc-update'); return true; } function isCloseToLimits() { if (config.maxWordCount > -1 && config.maxWordCount - lastWordCount < 5) { return true; } if (config.maxCharCount > -1 && config.maxCharCount - lastCharCount < 20) { return true; } if (config.maxParagraphs > -1 && config.maxParagraphs - lastParagraphs < 1) { return true; } return false; } editor.on('cp-wc', function(event) { clearTimeout(timeoutId); timeoutId = setTimeout( updateCounter.bind(this, event.editor), 250 ); }, editor, null, 250); } });