Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #2 from ckeditor/t/1
Browse files Browse the repository at this point in the history
Feature: Introduce word count feature. Closes #1. Closes ckeditor/ckeditor5#1301.
  • Loading branch information
Reinmar authored Jul 1, 2019
2 parents a87dd82 + 2aa90c8 commit 1fcd20d
Show file tree
Hide file tree
Showing 16 changed files with 1,165 additions and 2 deletions.
Empty file.
14 changes: 14 additions & 0 deletions docs/_snippets/features/build-word-count-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* globals window */

import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';

import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount';

ClassicEditor.builtinPlugins.push( WordCount );

window.ClassicEditor = ClassicEditor;
38 changes: 38 additions & 0 deletions docs/_snippets/features/word-count-update.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

<style>
.customized-count__color-box {
--hue: 180;
width: 20px;
height: 20px;
background-color: hsl( var( --hue ), 100%, 50% );
display: inline-block;
}

.customized-count {
border: 3px solid #333;
padding-left: 5px;
margin-bottom: 15px;
}

.customized-count > div {
display: inline-block;
width: 50%;
margin-left: 0;
margin-right: 0;
}
</style>

<div id="demo-editor-update">
<p>A <strong>black hole</strong> is a region of <a href="https://en.wikipedia.org/wiki/Spacetime">spacetime</a> exhibiting <a href="https://en.wikipedia.org/wiki/Gravitation">gravitational</a> acceleration so strong that nothing—no <a href="https://en.wikipedia.org/wiki/Particle">particles</a> or even <a href="https://en.wikipedia.org/wiki/Electromagnetic_radiation">electromagnetic radiation</a> such as <a href="https://en.wikipedia.org/wiki/Light">light</a>—can escape from it.<a href="https://en.wikipedia.org/wiki/Black_hole#cite_note-6">[6]</a> The theory of <a href="https://en.wikipedia.org/wiki/General_relativity">general relativity</a> predicts that a sufficiently compact <a href="https://en.wikipedia.org/wiki/Mass">mass</a> can deform <a href="https://en.wikipedia.org/wiki/Spacetime">spacetime</a> to form a black hole.</p>
</div>
<div class="customized-count">
<div class="customized-count__words">
<label>Words:
<progress value="42" max="100"></progress>
</label>
</div>
<div class="customized-count__characters">
Characters:
<div class="customized-count__color-box"></div>
</div>
</div>
48 changes: 48 additions & 0 deletions docs/_snippets/features/word-count-update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global window, document, console, ClassicEditor */

ClassicEditor
.create( document.querySelector( '#demo-editor-update' ), {
toolbar: {
items: [
'heading',
'bold',
'italic',
'bulletedList',
'numberedList',
'blockQuote',
'link',
'|',
'mediaEmbed',
'insertTable',
'|',
'undo',
'redo'
],
viewportTopOffset: window.getViewportTopOffsetConfig()
},
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
}
} )
.then( editor => {
const wordCountPlugin = editor.plugins.get( 'WordCount' );

const progressBar = document.querySelector( '.customized-count progress' );
const colorBox = document.querySelector( '.customized-count__color-box' );

wordCountPlugin.on( 'update', updateHandler );

function updateHandler( evt, payload ) {
progressBar.value = payload.words;
colorBox.style.setProperty( '--hue', payload.characters * 3 );
}
} )
.catch( err => {
console.error( err.stack );
} );

13 changes: 13 additions & 0 deletions docs/_snippets/features/word-count.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<style>
.word-count {
border: 3px solid #333;
padding-left: 5px;
margin-bottom: 15px;
}
</style>
<div id="demo-editor">
<p>The <strong>Battle of Westerplatte</strong> was one of the first battles in Germany's <a href="https://en.wikipedia.org/wiki/Invasion_of_Poland">invasion of Poland</a>, marking the start of <a href="https://en.wikipedia.org/wiki/World_War_II">World War II</a> in <a href="https://en.wikipedia.org/wiki/Europe">Europe</a>. Beginning on 1 September 1939, <a href="https://en.wikipedia.org/wiki/Nazi_Germany">German</a> <a href="https://en.wikipedia.org/wiki/German_Army_(Wehrmacht)">army</a>, <a href="https://en.wikipedia.org/wiki/Kriegsmarine">naval</a> and <a href="https://en.wikipedia.org/wiki/Luftwaffe">air forces</a> and <a href="https://en.wikipedia.org/wiki/Free_City_of_Danzig_Police">Danzig police</a> assaulted <a href="https://en.wikipedia.org/wiki/Poland">Poland</a>'s Military Transit Depot (<i>Wojskowa Składnica Tranzytowa</i>, or <i>WST</i>) on the <a href="https://en.wikipedia.org/wiki/Westerplatte">Westerplatte</a> peninsula in the harbor of the <a href="https://en.wikipedia.org/wiki/Free_City_of_Danzig">Free City of Danzig</a>. The Poles held out for seven days and repelled 13 assaults that included <a href="https://en.wikipedia.org/wiki/Dive_bomber">dive-bomber</a> attacks and naval shelling.</p>
<p>Westerplatte's defense served as an inspiration for the <a href="https://en.wikipedia.org/wiki/Polish_Army">Polish Army</a> and people in the face of German advances elsewhere, and is still regarded as a symbol of resistance in modern Poland.</p>
</div>
<div id="demo-word-count" class="word-count">
</div>
39 changes: 39 additions & 0 deletions docs/_snippets/features/word-count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* global document, window, console, ClassicEditor */

ClassicEditor
.create( document.querySelector( '#demo-editor' ), {
toolbar: {
items: [
'heading',
'bold',
'italic',
'bulletedList',
'numberedList',
'blockQuote',
'link',
'|',
'mediaEmbed',
'insertTable',
'|',
'undo',
'redo'
],
viewportTopOffset: window.getViewportTopOffsetConfig()
},
table: {
contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
}
} )
.then( editor => {
window.editor = editor;

document.getElementById( 'demo-word-count' ).appendChild( editor.plugins.get( 'WordCount' ).wordCountContainer );
} )
.catch( err => {
console.error( err.stack );
} );
104 changes: 104 additions & 0 deletions docs/features/word-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
category: features
---

{@snippet features/build-word-count-source}

# Word count

The {@link module:wordcount/wordcount~WordCount} feature provides a possibility to track the number of words and characters written in the editor.

## Demo

{@snippet features/word-count}

```html
<div id="editor">
<p>Hello world.</p>
</div>
<div id="word-count">
</div>
```

```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
// Configuration details.
} )
.then( editor => {
const wordCountPlugin = editor.plugins.get( 'WordCount' );
const wordCountWrapper = document.getElementById( 'word-count' );

wordCountWrapper.appendChild( wordCountPlugin.wordCountContainer );
} )
.catch( ... );
```

## Configuration options

There are two options which change the output container. If the {@link module:wordcount/wordcount~WordCountConfig#displayWords} is set to to `false`, then the section with word count is hidden. Similarly, when the {@link module:wordcount/wordcount~WordCountConfig#displayCharacters} is set to `false` it will hide the character counter.

## Update event

Word count feature emits an {@link module:wordcount/wordcount~WordCount#event:update update event} whenever there is a change in the model. This allows implementing customized behavior that reacts to word count updates.

Below you can find an example, where the background color of a square is changed according to the number of characters in the editor. There is also a progress bar which indicates how many words is in it (the maximal value of the progress bar is set to 100, however, you can write further and progress bar remain in the maximal state).

{@snippet features/word-count-update}

```js
ClassicEditor
.create( document.querySelector( '#editor' ), {
// configuration details
} )
.then( editor => {
const wordCountPlugin = editor.plugins.get( 'WordCount' );

wordCountPlugin.on( 'update', ( evt, data ) => {
// data is an object with "words" and "characters" field
doSthWithNewWordsNumber( data.words );
doSthWithNewCharactersNumber( data.characters );
} );

} )
.catch( ... );
```

## Installation

To add this feature to your rich-text editor, install the [`@ckeditor/ckeditor5-word-count`](https://www.npmjs.com/package/@ckeditor/ckeditor5-word-count) package:

```bash
npm install --save @ckeditor/ckeditor5-word-count
```

And add it to your plugin list configuration:

```js
import WordCount from '@ckeditor/ckeditor5-word-count/src/wordcount';

ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ WordCount, ... ],
} )
.then( ... )
.catch( ... );
```

<info-box info>
Read more about {@link builds/guides/integration/installing-plugins installing plugins}.
</info-box>

## Common API

The {@link module:wordcount/wordcount~WordCount} plugin provides:
* {@link module:wordcount/wordcount~WordCount#wordCountContainer} method. It returns a self-updating HTML element which is updated with the current number of words and characters in the editor. There is a possibility to remove "Words" or "Characters" counters with proper configuration of {@link module:wordcount/wordcount~WordCountConfig#displayWords} and {@link module:wordcount/wordcount~WordCountConfig#displayCharacters},
* {@link module:wordcount/wordcount~WordCount#event:update update event} which is fired whenever the plugins update the number of counted words and characters. There is a possibility to run own callback function with updated values. Please note that update event is throttled.

<info-box>
We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
</info-box>

## Contribute

The source code of the feature is available on GitHub in https://github.com/ckeditor/ckeditor5-word-count.
4 changes: 4 additions & 0 deletions lang/contexts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"Words: %0": "Label showing the number of words in the editor content.",
"Characters: %0": "Label showing the number of characters in the editor content."
}
17 changes: 15 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,25 @@
"ckeditor",
"ckeditor5",
"ckeditor 5",
"ckeditor5-feature",
"ckeditor5-plugin"
"ckeditor5-feature",
"ckeditor5-plugin"
],
"dependencies": {
"@ckeditor/ckeditor5-core": "^12.1.0",
"@ckeditor/ckeditor5-ui": "^13.0.0",
"lodash-es": "^4.17.10"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "^11.1.1",
"@ckeditor/ckeditor5-block-quote": "^11.1.0",
"@ckeditor/ckeditor5-editor-classic": "^12.1.1",
"@ckeditor/ckeditor5-engine": "^13.1.1",
"@ckeditor/ckeditor5-enter": "^11.0.2",
"@ckeditor/ckeditor5-link": "^11.0.2",
"@ckeditor/ckeditor5-list": "^12.0.2",
"@ckeditor/ckeditor5-paragraph": "^11.0.2",
"@ckeditor/ckeditor5-table": "^13.0.0",
"@ckeditor/ckeditor5-utils": "^12.1.1",
"eslint": "^5.5.0",
"eslint-config-ckeditor5": "^1.0.11",
"husky": "^1.3.1",
Expand Down
38 changes: 38 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @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
*/

/**
* Returns a plain text representation of an element and its children.
*
* @param {module:engine/model/element~Element} element
* @returns {String} Plain text representing model's data
*/
export function modelElementToPlainText( element ) {
if ( element.is( 'text' ) || element.is( 'textProxy' ) ) {
return element.data;
}

let text = '';
let prev = null;

for ( const child of element.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;
}
Loading

0 comments on commit 1fcd20d

Please sign in to comment.