Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

## Unreleased

- Added the “Field Limit” setting, which can be set to a character limit or word limit. ([#384](https://github.com/craftcms/ckeditor/pull/384))
- The “Anchors” CKEditor plugin has been replaced with CKEditor’s new built-in [Bookmarks](https://ckeditor.com/docs/ckeditor5/latest/features/bookmarks.html) plugin. ([#397](https://github.com/craftcms/ckeditor/pull/397))
- Updated to CKEditor 5 45.0.0. ([#397](https://github.com/craftcms/ckeditor/pull/397))
- Added `craft\ckeditor\Field::$characterLimit`.

## 3.12.0 - 2025-04-21

Expand Down
53 changes: 51 additions & 2 deletions src/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ public static function textPartLanguage(): array
*/
public ?int $wordLimit = null;

/**
* @var int|null The total number of characters allowed.
* @since 3.13.0
*/
public ?int $characterLimit = null;

/**
* @var bool Whether the word count should be shown below the field.
* @since 3.2.0
Expand Down Expand Up @@ -179,6 +185,15 @@ public function __construct($config = [])
$config['sourceEditingGroups'] = null;
}

if (isset($config['limitUnit'], $config['fieldLimit'])) {
if ($config['limitUnit'] === 'chars') {
$config['characterLimit'] = (int)$config['fieldLimit'] ?: null;
} else {
$config['wordLimit'] = (int)$config['fieldLimit'] ?: null;
}
unset($config['limitUnit'], $config['fieldLimit']);
}

parent::__construct($config);
}

Expand All @@ -192,6 +207,9 @@ public function init(): void
if ($this->wordLimit === 0) {
$this->wordLimit = null;
}
if ($this->characterLimit === 0) {
$this->characterLimit = null;
}
}

/**
Expand All @@ -201,6 +219,7 @@ protected function defineRules(): array
{
return array_merge(parent::defineRules(), [
['wordLimit', 'number', 'min' => 1],
['characterLimit', 'number', 'min' => 1],
]);
}

Expand All @@ -211,7 +230,22 @@ public function getElementValidationRules(): array
{
$rules = [];

if ($this->wordLimit) {
if ($this->characterLimit) {
$rules[] = [
function(ElementInterface $element) {
$value = strip_tags((string)$element->getFieldValue($this->handle));
if (strlen($value) > $this->characterLimit) {
$element->addError(
"field:$this->handle",
Craft::t('ckeditor', '{field} should contain at most {max, number} {max, plural, one{character} other{characters}}.', [
'field' => Craft::t('site', $this->name),
'max' => $this->characterLimit,
]),
);
}
},
];
} elseif ($this->wordLimit) {
$rules[] = [
function(ElementInterface $element) {
$value = html_entity_decode((string)$element->getFieldValue($this->handle));
Expand Down Expand Up @@ -430,7 +464,13 @@ protected function inputHtml(mixed $value, ElementInterface $element = null): st
'textPartLanguage' => static::textPartLanguage(),
]);
$showWordCountJs = Json::encode($this->showWordCount);
$wordLimitJs = $this->wordLimit ?: 0;
$wordLimitJs = 0;
$characterLimitJs = 0;
if ($this->characterLimit) {
$characterLimitJs = $this->characterLimit;
} elseif ($this->wordLimit) {
$wordLimitJs = $this->wordLimit;
}

$view->registerJs(<<<JS
(($) => {
Expand Down Expand Up @@ -473,6 +513,15 @@ protected function inputHtml(mixed $value, ElementInterface $element = null): st
container.removeClass('error warning');
}
}
if ($characterLimitJs) {
if (stats.characters > $characterLimitJs) {
container.addClass('error');
} else if (stats.characters >= Math.floor($characterLimitJs * .9)) {
container.addClass('warning');
} else {
container.removeClass('error warning');
}
}
onUpdate(stats);
}
} else {
Expand Down
38 changes: 27 additions & 11 deletions src/templates/_field-settings.twig
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,33 @@
{% endblock %}
{% endembed %}

{{ forms.textField({
id: 'word-limit',
name: 'wordLimit',
label: 'Word Limit'|t('ckeditor'),
value: field.wordLimit,
type: 'number',
min: 0,
step: 1,
size: 5,
errors: field.getErrors('wordLimit'),
}) }}
{% embed '_includes/forms/field' with {
label: 'Field Limit'|t('app'),
instructions: "The maximum number of words or characters the field is allowed to have."|t('ckeditor'),
id: 'field-limit',
errors: field.getErrors(field.wordLimit ? 'wordLimit' : 'characterLimit')
} %}
{% import "_includes/forms" as forms %}
{% block input %}
<div class="flex">
{{ forms.text({
id: 'fieldLimit',
name: 'fieldLimit',
value: field.wordLimit ?? field.characterLimit,
size: 3,
}) }}
{{ forms.select({
id: 'limitUnit',
name: 'limitUnit',
options: [
{ value: 'words', label: 'Words'|t('ckeditor') },
{ value: 'chars', label: 'Characters'|t('app') },
],
value: field.characterLimit ? 'chars' : 'words'
}) }}
</div>
{% endblock %}
{% endembed %}

{{ forms.lightswitchField({
id: 'show-word-count',
Expand Down
4 changes: 3 additions & 1 deletion src/translations/en/ckeditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@
'Show word count' => 'Show word count',
'Site: {name}' => 'Site: {name}',
'The default transform that should be applied when inserting an image.' => 'The default transform that should be applied when inserting an image.',
'The maximum number of words or characters the field is allowed to have.' => 'The maximum number of words or characters the field is allowed to have.',
'The transforms that should be available when inserting images.' => 'The transforms that should be available when inserting images.',
'The type of column this field should get in the database.' => 'The type of column this field should get in the database.',
'Toolbar' => 'Toolbar',
'View available settings' => 'View available settings',
'Who should see the “Source” button?' => 'Who should see the “Source” button?',
'Word Limit' => 'Word Limit',
'Words' => 'Words',
'You can save custom {name} configs as {ext} files in {path}.' => 'You can save custom {name} configs as {ext} files in {path}.',
'Your JavaScript config contains functions. If you switch to JSON, they will be lost. Would you like to continue?',
'{attribute} isn’t valid JSON.' => '{attribute} isn’t valid JSON.',
'{field} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{field} should contain at most {max, number} {max, plural, one{character} other{characters}}.',
'{field} should contain at most {max, number} {max, plural, one{word} other{words}}.' => '{field} should contain at most {max, number} {max, plural, one{word} other{words}}.',
];
Loading