Parcourir la source

Add plugins base.

Mateusz Samsel il y a 6 ans
Parent
commit
1f027dedff

+ 42 - 0
packages/ckeditor5-word-count/src/utils.js

@@ -0,0 +1,42 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module wordcount/utils
+ */
+
+/**
+ * Function walks through all model's nodes. It obtains a plain text from each {@link module:engine/model/text~Text}
+ * and {@link module:engine/model/textproxy~TextProxy}. All sections, which are not a text, are separated with a new line (`\n`).
+ *
+ * **Note:** Function walks through entire model. There should be considered throttling it during usage.
+ *
+ * @param {module:engine/model/node~Node} node
+ * @returns {String} Plain text representing model's data
+ */
+export function modelElementToPlainText( node ) {
+	let text = '';
+
+	if ( node.is( 'text' ) || node.is( 'textProxy' ) ) {
+		text += node.data;
+	} else {
+		let prev = null;
+
+		for ( const child of node.getChildren() ) {
+			const childText = modelElementToPlainText( child );
+
+			// If last block was finish, start from new line.
+			if ( prev && prev.is( 'element' ) ) {
+				text += '\n';
+			}
+
+			text += childText;
+
+			prev = child;
+		}
+	}
+
+	return text;
+}

+ 207 - 0
packages/ckeditor5-word-count/src/wordcount.js

@@ -0,0 +1,207 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module wordcount/wordcount
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import { modelElementToPlainText } from './utils';
+import { throttle } from 'lodash-es';
+import View from '@ckeditor/ckeditor5-ui/src/view';
+import Template from '@ckeditor/ckeditor5-ui/src/template';
+
+/**
+ * The word count plugin.
+ *
+ * This plugin calculate all words and characters in all {@link module:engine/model/text~Text text nodes} available in the model.
+ * It also provides an HTML element, which updates it states whenever editor's content is changed.
+ *
+ * Firstly model's data are convert to plain text using {@link module:wordcount/utils.modelElementToPlainText}.
+ * Based on such created plain text there are determined amount of words and characters in your text. Please keep in mind
+ * that every block in editor is separate with a new line character, which is included into calculation.
+ *
+ * Few examples how word and character calculation are made:
+ * 		<paragraph>foo</paragraph>
+ * 		<paragraph>bar</paragraph>
+ * 		// Words: 2, Characters: 7
+ *
+ * 		<paragraph><$text bold="true">foo</$text>bar</paragraph>
+ * 		// Words: 1, Characters: 6
+ *
+ * 		<paragraph>*&^%)</paragraph>
+ * 		// Words: 0, Characters: 5
+ *
+ * 		<paragraph>foo(bar)</paragraph>
+ * 		//Words: 2, Characters: 8
+ *
+ * 		<paragraph>12345</paragraph>
+ * 		// Words: 1, Characters: 5
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class WordCount extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		/**
+		 * Property stores number of characters detected by {@link module:wordcount/wordcount~WordCount WordCount plugin}.
+		 *
+		 * @observable
+		 * @readonly
+		 * @member {Number} module:wordcount/wordcount~WordCount#characters
+		 */
+		this.set( 'characters', 0 );
+
+		/**
+		 * Property stores number of words detected by {@link module:wordcount/wordcount~WordCount WordCount plugin}.
+		 *
+		 * @observable
+		 * @readonly
+		 * @member {Number} module:wordcount/wordcount~WordCount#words
+		 */
+		this.set( 'words', 0 );
+
+		/**
+		 * Keeps reference to {@link module:ui/view~View view object} used to display self-updating HTML container.
+		 *
+		 * @private
+		 * @readonly
+		 * @type {module:ui/view~View}
+		 */
+		this._outputView;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'WordCount';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+
+		editor.model.document.on( 'change', throttle( this._calcWordsAndCharacters.bind( this ), 250 ) );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		this._outputView.element.remove();
+		this._outputView.destroy();
+
+		super.destroy();
+	}
+
+	/**
+	 * Method creates self-updated HTML element. Repeated executions returns the same element.
+	 * Returned element has followed HTML structure:
+	 *
+	 * 		<div class="ck ck-word-count">
+	 * 			<div>Words: 4</div>
+	 * 			<div>Characters: 28</div>
+	 * 		</div>
+	 *
+	 * @returns {HTMLElement}
+	 */
+	getWordCountContainer() {
+		if ( !this._outputView ) {
+			this._outputView = new View();
+
+			const editor = this.editor;
+			const t = editor.t;
+			const displayWords = editor.config.get( 'wordCount.displayWords' );
+			const displayCharacters = editor.config.get( 'wordCount.displayCharacters' );
+			const bind = Template.bind( this, this );
+			const children = [];
+
+			if ( displayWords || displayWords === undefined ) {
+				const wordsLabel = t( 'Words' );
+
+				children.push( {
+					tag: 'div',
+					children: [
+						{
+							text: [
+								wordsLabel,
+								': ',
+								bind.to( 'words' )
+							]
+						}
+					]
+				} );
+			}
+
+			if ( displayCharacters || displayCharacters === undefined ) {
+				const charactersLabel = t( 'Characters' );
+
+				children.push( {
+					tag: 'div',
+					children: [
+						{
+							text: [
+								charactersLabel,
+								': ',
+								bind.to( 'characters' )
+							]
+						}
+					]
+				} );
+			}
+
+			this._outputView.setTemplate( {
+				tag: 'div',
+				attributes: {
+					class: [
+						'ck',
+						'ck-word-count'
+					]
+				},
+				children
+			} );
+
+			this._outputView.render();
+		}
+
+		return this._outputView.element;
+	}
+
+	/**
+	 * Determines amount of words and characters in current editor's model and assigns it to {@link #characters} and {@link #words}.
+	 * It also fires {@link #event:update}.
+	 *
+	 * @private
+	 * @fires update
+	 */
+	_calcWordsAndCharacters() {
+		const txt = modelElementToPlainText( this.editor.model.document.getRoot() );
+
+		this.characters = txt.length;
+
+		this.words = ( txt.match( /[_a-zA-Z0-9À-ž]+/gu ) || [] ).length;
+
+		this.fire( 'update', {
+			words: this.words,
+			characters: this.characters
+		} );
+	}
+}
+
+/**
+ * Event is fired after {@link #words} and {@link #characters} are updated.
+ *
+ * @event update
+ * @param {Object} data
+ * @param {Number} data.words number of words in current model
+ * @param {Number} data.characters number of characters in current model
+ */