mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
🔢 Add wordcount feature. (#668)
closes TryGhost/Ghost#8202 - added wordcount for mobiledoc text and html/markdown cards (cards will only update word count when leaving edit state) - word count is only displayed on wide screens
This commit is contained in:
parent
c6ed77a0fc
commit
e604b255af
@ -42,6 +42,7 @@ export default Mixin.create({
|
||||
clock: injectService(),
|
||||
slugGenerator: injectService(),
|
||||
|
||||
wordcount: 0,
|
||||
cards: [], // for apps
|
||||
atoms: [], // for apps
|
||||
toolbar: [], // for apps
|
||||
@ -580,6 +581,10 @@ export default Mixin.create({
|
||||
|
||||
editorMenuIsClosed() {
|
||||
this.set('editorMenuIsOpen', false);
|
||||
},
|
||||
|
||||
wordcountDidChange(wordcount) {
|
||||
this.set('wordcount', wordcount);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -251,7 +251,18 @@
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
|
||||
.gh-editor-header-small .post-settings {
|
||||
padding: 13px 15px;
|
||||
}
|
||||
|
||||
.gh-editor-wordcount {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
padding:10px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.gh-editor-wordcount {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -50,9 +50,11 @@
|
||||
setEditor=(action "setEditor")
|
||||
menuIsOpen=(action "editorMenuIsOpen")
|
||||
menuIsClosed=(action "editorMenuIsClosed")
|
||||
wordcountDidChange=(action "wordcountDidChange")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="gh-editor-wordcount">{{pluralize wordcount 'word'}}.</div>
|
||||
{{/gh-editor}}
|
||||
|
||||
{{#if showDeletePostModal}}
|
||||
|
@ -3,13 +3,16 @@ import layout from '../../templates/components/card-html';
|
||||
import computed from 'ember-computed';
|
||||
import observer from 'ember-metal/observer';
|
||||
import {invokeAction} from 'ember-invoke-action';
|
||||
import counter from 'ghost-admin/utils/word-count';
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
hasRendered: false,
|
||||
|
||||
save: observer('doSave', function () {
|
||||
this.get('env').save(this.get('payload'), false);
|
||||
let payload = this.get('payload');
|
||||
payload.wordcount = counter(payload.html);
|
||||
this.get('env').save(payload, false);
|
||||
}),
|
||||
|
||||
value: computed('payload', {
|
||||
|
@ -8,6 +8,7 @@ import {isBlank} from 'ember-utils';
|
||||
import computed from 'ember-computed';
|
||||
import observer from 'ember-metal/observer';
|
||||
import run from 'ember-runloop';
|
||||
import counter from 'ghost-admin/utils/word-count';
|
||||
import {
|
||||
isRequestEntityTooLargeError,
|
||||
isUnsupportedMediaTypeError,
|
||||
@ -29,6 +30,7 @@ export default Component.extend({
|
||||
save: observer('doSave', function () {
|
||||
let payload = this.get('payload');
|
||||
payload.markdown = this.$('textarea').val();
|
||||
payload.wordcount = counter(payload.markdown);
|
||||
this.set('value', this.$('textarea').val());
|
||||
this.set('payload', payload);
|
||||
this.get('env').save(payload, false);
|
||||
|
@ -8,8 +8,8 @@ import createCardFactory from '../lib/card-factory';
|
||||
import defaultCommands from '../options/default-commands';
|
||||
import editorCards from '../cards/index';
|
||||
import {getCardFromDoc, checkIfClickEventShouldCloseCard, getPositionOnScreenFromRange} from '../lib/utils';
|
||||
import counter from 'ghost-admin/utils/word-count';
|
||||
import $ from 'jquery';
|
||||
// import { VALID_MARKUP_SECTION_TAGNAMES } from 'mobiledoc-kit/models/markup-section'; //the block elements supported by mobile-doc
|
||||
|
||||
export const BLANK_DOC = {
|
||||
version: MOBILEDOC_VERSION,
|
||||
@ -103,6 +103,7 @@ export default Component.extend({
|
||||
this._firstChange = true;
|
||||
this.sendAction('onFirstChange', this._cachedDoc);
|
||||
}
|
||||
this.processWordcount();
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -151,6 +152,7 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
editor.cursorDidChange(() => this.cursorMoved());
|
||||
this.processWordcount();
|
||||
},
|
||||
|
||||
// makes sure the cursor is on screen except when selection is happening in which case the browser mostly ensures it.
|
||||
@ -193,7 +195,21 @@ export default Component.extend({
|
||||
this.send('deselectCard');
|
||||
}
|
||||
},
|
||||
|
||||
// Note: This wordcount function doesn't count words that have been entered in cards.
|
||||
// We should either allow cards to report their own wordcount or use the DOM (innerText) to calculate the wordcount.
|
||||
processWordcount() {
|
||||
let wordcount = 0;
|
||||
if (this.editor.post.sections.length) {
|
||||
this.editor.post.sections.forEach((section) => {
|
||||
if (section.isMarkerable && section.text.length) {
|
||||
wordcount += counter(section.text);
|
||||
} else if (section.isCardSection && section.payload.wordcount) {
|
||||
wordcount += Number(section.payload.wordcount);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.sendAction('wordcountDidChange', wordcount);
|
||||
},
|
||||
willDestroy() {
|
||||
this.editor.destroy();
|
||||
this.send('deselectCard');
|
||||
|
@ -17,7 +17,7 @@
|
||||
{{/ember-wormhole}}
|
||||
{{/each}}
|
||||
<div class='gh-koenig'>
|
||||
<div class='surface needsclick' tabindex="{{tabindex}}" ondrop={{action "dropImage"}} ondragover={{action "dragOver"}} />
|
||||
<div class='surface' tabindex="{{tabindex}}" ondrop={{action "dropImage"}} ondragover={{action "dragOver"}} />
|
||||
</div>
|
||||
|
||||
{{yield}}
|
||||
|
56
ghost/admin/tests/integration/components/gh-koenig-test.js
Normal file
56
ghost/admin/tests/integration/components/gh-koenig-test.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* jshint expr:true */
|
||||
import {describe, it} from 'mocha';
|
||||
import {setupComponentTest} from 'ember-mocha';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import {editorRendered, testInput} from '../../helpers/editor-helpers';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe.skip('Integration: Component: gh-koenig - General Editor Tests.', function () {
|
||||
setupComponentTest('gh-koenig', {
|
||||
integration: true
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
// set defaults
|
||||
this.set('onFirstChange', sinon.spy());
|
||||
this.set('onChange', sinon.spy());
|
||||
|
||||
this.set('wordcount', 0);
|
||||
this.set('actions.wordcountDidChange', function (wordcount) {
|
||||
this.set('wordcount', wordcount);
|
||||
});
|
||||
|
||||
this.set('value', {
|
||||
version: '0.3.1',
|
||||
atoms: [],
|
||||
markups: [],
|
||||
cards: [],
|
||||
sections: []});
|
||||
|
||||
});
|
||||
|
||||
it('Check that events have fired', function (done) {
|
||||
this.render(hbs`{{gh-koenig
|
||||
apiRoot='/todo'
|
||||
assetPath='/assets'
|
||||
containerSelector='.editor-holder'
|
||||
value=value
|
||||
onChange=(action onChange)
|
||||
onFirstChange=(action onFirstChange)
|
||||
wordcountDidChange=(action 'wordcountDidChange')
|
||||
}}`);
|
||||
|
||||
editorRendered()
|
||||
.then(() => {
|
||||
let {editor} = window;
|
||||
editor.element.focus();
|
||||
return testInput('abcd efg hijk lmnop', '<p>abcd efg hijk lmnop</p>', expect);
|
||||
})
|
||||
.then(() => {
|
||||
expect(this.get('onFirstChange').calledOnce).to.be.true;
|
||||
expect(this.get('onChange').calledOnce).to.be.true;
|
||||
expect(this.get('wordcount')).to.equal(4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user