Skip to content

Commit 6291073

Browse files
committed
“Show word count” field setting
Resolves #75
1 parent 29d3204 commit 6291073

File tree

7 files changed

+112
-17
lines changed

7 files changed

+112
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Added the “Show word count” field setting. ([#75](https://github.com/craftcms/ckeditor/issues/75))
56
- Added the [Font](https://ckeditor.com/docs/ckeditor5/latest/features/font.html#configuring-the-font-size-feature) CKEditor plugin, which provides “Font Size” “Font Family”, “Font Color”, and “Font Background Color” toolbar buttons.
67
- Ordered/unordered list buttons now include [additional options](https://ckeditor.com/docs/ckeditor5/latest/features/lists/lists.html#list-properties).
78
- Config options are now automatically updated when “Heading”, “Style”, “Alignment”, and “Font Size” buttons are added/removed in the toolbar. ([#73](https://github.com/craftcms/ckeditor/pull/73))

src/Field.php

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ public static function displayName(): string
7979
*/
8080
public ?string $ckeConfig = null;
8181

82+
/**
83+
* @var bool Whether the word count should be shown below the field.
84+
* @since 3.2.0
85+
*/
86+
public bool $showWordCount = false;
87+
8288
/**
8389
* @var string|array|null The volumes that should be available for image selection.
8490
* @since 1.2.0
@@ -184,6 +190,9 @@ protected function inputHtml(mixed $value, ElementInterface $element = null): st
184190

185191
$id = Html::id($this->handle);
186192
$idJs = Json::encode($view->namespaceInputId($id));
193+
$wordCountId = "$id-counts";
194+
$wordCountIdJs = Json::encode($view->namespaceInputId($wordCountId));
195+
187196
$baseConfig = [
188197
'ui' => [
189198
'viewportOffset' => ['top' => 50],
@@ -245,9 +254,45 @@ protected function inputHtml(mixed $value, ElementInterface $element = null): st
245254
}
246255

247256
$baseConfigJs = Json::encode($event->baseConfig);
257+
$showWordCountJs = Json::encode($this->showWordCount);
248258

249259
$view->registerJs(<<<JS
250-
Ckeditor.create($idJs, Object.assign($baseConfigJs, $configOptionsJs));
260+
(($) => {
261+
const config = Object.assign($baseConfigJs, $configOptionsJs);
262+
const extraRemovePlugins = [];
263+
if ($showWordCountJs) {
264+
if (typeof config.wordCount === 'undefined') {
265+
config.wordCount = {};
266+
}
267+
const onUpdate = config.wordCount.onUpdate || (() => {});
268+
config.wordCount.onUpdate = (stats) => {
269+
const statText = [];
270+
if (config.wordCount.displayWords || typeof config.wordCount.displayWords === 'undefined') {
271+
statText.push(Craft.t('ckeditor', '{num, number} {num, plural, =1{word} other{words}}', {
272+
num: stats.words,
273+
}));
274+
}
275+
if (config.wordCount.displayCharacters) { // false by default
276+
statText.push(Craft.t('ckeditor', '{num, number} {num, plural, =1{character} other{characters}}', {
277+
num: stats.characters,
278+
}));
279+
}
280+
$('#' + $wordCountIdJs).html(Craft.escapeHtml(statText.join(', ')) || '&nbsp;');
281+
onUpdate(stats);
282+
}
283+
} else {
284+
extraRemovePlugins.push('WordCount');
285+
}
286+
if (extraRemovePlugins.length) {
287+
if (typeof config.removePlugins === 'undefined') {
288+
config.removePlugins = [];
289+
}
290+
config.removePlugins.push(...extraRemovePlugins);
291+
console.log(config);
292+
}
293+
Ckeditor.create($idJs, config).then((editor) => {
294+
});
295+
})(jQuery)
251296
JS,
252297
View::POS_END,
253298
);
@@ -256,12 +301,23 @@ protected function inputHtml(mixed $value, ElementInterface $element = null): st
256301
$view->registerCss($ckeConfig->css);
257302
}
258303

259-
return Html::tag('div',
260-
Html::textarea($this->handle, $this->prepValueForInput($value, $element), [
261-
'id' => $id,
262-
'class' => 'hidden',
263-
]),
264-
);
304+
$html = Html::textarea($this->handle, $this->prepValueForInput($value, $element), [
305+
'id' => $id,
306+
'class' => 'hidden',
307+
]);
308+
309+
if ($this->showWordCount) {
310+
$html .= Html::tag('div', '&nbps;', [
311+
'id' => $wordCountId,
312+
'class' => ['ck-word-count', 'light', 'smalltext'],
313+
]);
314+
}
315+
316+
return Html::tag('div', $html, [
317+
'class' => array_filter([
318+
$this->showWordCount ? 'ck-with-show-word-count' : null,
319+
])
320+
]);
265321
}
266322

267323
/**

src/console/controllers/ConvertController.php

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ public function actionRedactor(): int
179179
// Map the Redactor configs to CKEditor configs
180180
$this->ckeConfigs = Plugin::getInstance()->getCkeConfigs();
181181
$ckeConfigs = $this->ckeConfigs->getAll();
182+
$fieldSettingsByConfig = [];
182183
$configMap = [];
183184

184185
foreach ($fields as $path => $field) {
@@ -195,13 +196,13 @@ public function actionRedactor(): int
195196
throw new Exception('`manualConfig` contains invalid JSON.');
196197
}
197198
$configName = $field['name'] ?? (!empty($field['handle']) ? Inflector::camel2words($field['handle']) : 'Untitled');
198-
$ckeConfig = $this->generateCkeConfig($configName, $redactorConfig, $ckeConfigs);
199+
$ckeConfig = $this->generateCkeConfig($configName, $redactorConfig, $ckeConfigs, $fieldSettingsByConfig);
199200
$this->stdout(PHP_EOL);
200201
} else {
201202
$basename = ($field['settings']['redactorConfig'] ?? null) ?: 'Default.json';
202203
if (!isset($configMap[$basename])) {
203204
$this->stdout(PHP_EOL . PHP_EOL);
204-
$configMap[$basename] = $this->resolveRedactorConfig($basename, $ckeConfigs);
205+
$configMap[$basename] = $this->resolveRedactorConfig($basename, $ckeConfigs, $fieldSettingsByConfig);
205206
$this->stdout(PHP_EOL);
206207
}
207208
$ckeConfig = $configMap[$basename];
@@ -210,6 +211,10 @@ public function actionRedactor(): int
210211
$field['type'] = Field::class;
211212
$field['settings']['ckeConfig'] = $ckeConfig;
212213

214+
if (isset($fieldSettingsByConfig[$ckeConfig])) {
215+
$field['settings'] = array_merge($field['settings'], $fieldSettingsByConfig[$ckeConfig]);
216+
}
217+
213218
unset(
214219
$field['settings']['cleanupHtml'],
215220
$field['settings']['configSelectionMode'],
@@ -280,11 +285,15 @@ private function pathAndHandleMarkdown(string $path, array $config): string
280285
/**
281286
* @param string $basename
282287
* @param CkeConfig[] $ckeConfigs
288+
* @param array[] $fieldSettingsByConfig
283289
* @return string
284290
* @throws \Exception
285291
*/
286-
private function resolveRedactorConfig(string $basename, array &$ckeConfigs): string
287-
{
292+
private function resolveRedactorConfig(
293+
string $basename,
294+
array &$ckeConfigs,
295+
array &$fieldSettingsByConfig,
296+
): string {
288297
$filename = pathinfo($basename, PATHINFO_FILENAME);
289298
$this->stdout(' ');
290299
if ($this->confirm($this->markdownToAnsi("Do you already have a CKEditor config that should be used in place of the `$filename` Redactor config?"))) {
@@ -299,17 +308,23 @@ private function resolveRedactorConfig(string $basename, array &$ckeConfigs): st
299308
$redactorConfig = [];
300309
}
301310

302-
return $this->generateCkeConfig(Inflector::camel2words($filename), $redactorConfig, $ckeConfigs);
311+
return $this->generateCkeConfig(Inflector::camel2words($filename), $redactorConfig, $ckeConfigs, $fieldSettingsByConfig);
303312
}
304313

305314
/**
315+
* @param string $configName
306316
* @param array $redactorConfig
307317
* @param CkeConfig[] $ckeConfigs
318+
* @param array[] $fieldSettingsByConfig
308319
* @return string
309320
* @throws OperationAbortedException
310321
*/
311-
private function generateCkeConfig(string $configName, array $redactorConfig, array &$ckeConfigs): string
312-
{
322+
private function generateCkeConfig(
323+
string $configName,
324+
array $redactorConfig,
325+
array &$ckeConfigs,
326+
array &$fieldSettingsByConfig,
327+
): string {
313328
// Make sure the name is unique
314329
$baseConfigName = $configName;
315330
$attempt = 1;
@@ -438,6 +453,9 @@ private function generateCkeConfig(string $configName, array $redactorConfig, ar
438453
switch ($plugin) {
439454
case 'alignment': $addButton('alignment'); break;
440455
case 'clips': $addButton('clips'); break;
456+
case 'counter':
457+
$fieldSettingsByConfig[$ckeConfig->uid]['showWordCount'] = true;
458+
break;
441459
case 'fontcolor': $addButton('fontcolor'); break;
442460
case 'fontfamily': $addButton('fontfamily'); break;
443461
case 'fontsize': $addButton('fontsize'); break;

src/templates/_field-settings.twig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@
3434
{% endblock %}
3535
{% endembed %}
3636

37+
{{ forms.lightswitchField({
38+
id: 'show-word-count',
39+
name: 'showWordCount',
40+
label: 'Show word count'|t('ckeditor'),
41+
on: field.showWordCount
42+
}) }}
43+
3744
<hr>
3845

3946
<a class="fieldtoggle" data-target="assets">{{ 'Assets'|t('app') }}</a>

src/web/assets/ckeditor/dist/css/ckeditor.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)