Skip to content

Instantly share code, notes, and snippets.

@mherchel
Created March 23, 2023 12:19
Show Gist options
  • Save mherchel/8a6dbc1a60afbbd7558b91057d12fd59 to your computer and use it in GitHub Desktop.
Save mherchel/8a6dbc1a60afbbd7558b91057d12fd59 to your computer and use it in GitHub Desktop.
diff --git a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml
index 4396b64ed4..0372fddc89 100644
--- a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml
+++ b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml
@@ -263,6 +263,7 @@ ckeditor5_codeBlock:
label: Code Block
library: ckeditor5/internal.drupal.ckeditor5.codeBlock
admin_library: ckeditor5/internal.admin.codeBlock
+ class: Drupal\ckeditor5\Plugin\CKEditor5Plugin\CodeBlock
toolbar_items:
codeBlock:
label: Code Block
diff --git a/core/modules/ckeditor5/ckeditor5.post_update.php b/core/modules/ckeditor5/ckeditor5.post_update.php
index 1e56e029c1..e7d18c19fc 100644
--- a/core/modules/ckeditor5/ckeditor5.post_update.php
+++ b/core/modules/ckeditor5/ckeditor5.post_update.php
@@ -100,3 +100,19 @@ function ckeditor5_post_update_image_toolbar_item(&$sandbox = []) {
$config_entity_updater->update($sandbox, 'editor', $callback);
}
+
+/**
+ * Updates Text Editors using CKEditor 5 Code Block.
+ */
+function ckeditor5_post_update_code_block(&$sandbox = []) {
+ $config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class);
+ $config_entity_updater->update($sandbox, 'editor', function (Editor $editor): bool {
+ // Only try to update editors using CKEditor 5.
+ if ($editor->getEditor() !== 'ckeditor5') {
+ return FALSE;
+ }
+ $settings = $editor->getSettings();
+
+ return in_array('codeBlock', $settings['toolbar']['items'], TRUE);
+ });
+}
diff --git a/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml b/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
index b7b2bc3b20..7c1d527ae6 100644
--- a/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
+++ b/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
@@ -136,6 +136,31 @@ ckeditor5.plugin.media_media:
constraints:
NotNull: []
+# Plugin \Drupal\ckeditor5\Plugin\CKEditor5Plugin\CodeBlock
+ckeditor5.plugin.ckeditor5_codeBlock:
+ type: mapping
+ label: Code Block
+ mapping:
+ languages:
+ type: sequence
+ orderby: ~
+ label: 'Languages'
+ constraints:
+ NotBlank:
+ message: "Enable at least one language, otherwise disable the Code Block plugin."
+ UniqueLabelInList:
+ labelKey: label
+ sequence:
+ type: mapping
+ label: 'Language'
+ mapping:
+ label:
+ type: label
+ label: 'Language label'
+ language:
+ type: string
+ label: 'Language key'
+
# Plugin \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Style
ckeditor5.plugin.ckeditor5_style:
type: mapping
diff --git a/core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/CodeBlock.php b/core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/CodeBlock.php
new file mode 100644
index 0000000000..2f05a6f231
--- /dev/null
+++ b/core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/CodeBlock.php
@@ -0,0 +1,151 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\ckeditor5\Plugin\CKEditor5Plugin;
+
+use Drupal\ckeditor5\HTMLRestrictions;
+use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableTrait;
+use Drupal\ckeditor5\Plugin\CKEditor5PluginDefault;
+use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableInterface;
+use Drupal\ckeditor5\Plugin\CKEditor5PluginElementsSubsetInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\editor\EditorInterface;
+
+/**
+ * CKEditor 5 Code Block plugin configuration.
+ *
+ * @internal
+ * Plugin classes are internal.
+ */
+class CodeBlock extends CKEditor5PluginDefault implements CKEditor5PluginConfigurableInterface, CKEditor5PluginElementsSubsetInterface {
+
+ use CKEditor5PluginConfigurableTrait;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form['languages'] = [
+ '#title' => $this->t('Code Block Languages'),
+ '#type' => 'textarea',
+ '#description' => $this->t('A list of code block languages that will be provided in the "Code Block" dropdown. Enter one value per line, in the format key|label. Example: php|PHP'),
+ ];
+ if (!empty($this->configuration['languages'])) {
+ $as_selectors = '';
+ foreach ($this->configuration['languages'] as $language) {
+ $as_selectors .= sprintf("%s|%s\n", $language['language'], $language['label']);
+ }
+ $form['languages']['#default_value'] = $as_selectors;
+ }
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ $form_value = $form_state->getValue('languages');
+ [$styles, $unparseable_lines] = self::parseLanguagesFromValue($form_value);
+ if (!empty($unparseable_lines)) {
+ $line_numbers = array_keys($unparseable_lines);
+ $form_state->setError($form['languages'], $this->formatPlural(
+ count($unparseable_lines),
+ 'Line @line-number does not contain a valid value. Enter a valid language key followed by a pipe symbol and a label.',
+ 'Lines @line-numbers do not contain a valid value. Enter a valid language key followed by a pipe symbol and a label.',
+ [
+ '@line-number' => reset($line_numbers),
+ '@line-numbers' => implode(', ', $line_numbers),
+ ]
+ ));
+ }
+ $form_state->setValue('languages', $styles);
+ }
+
+ /**
+ * Parses the line-based (for form) Code Block configuration.
+ *
+ * @param string $form_value
+ * A string containing >=1 lines with on each line a language key and label.
+ *
+ * @return array
+ * The parsed equivalent: a list of arrays with each containing:
+ * - label: the label after the pipe symbol, with whitespace trimmed
+ * - language: the key for the language
+ *
+ * @internal
+ * This method is public only to allow the CKEditor 4 to 5 upgrade path to
+ * reuse this logic. Mark this private in https://www.drupal.org/i/3239012.
+ */
+ public static function parseLanguagesFromValue(string $form_value): array {
+ $unparseable_lines = [];
+
+ $lines = explode("\n", $form_value);
+ $languages = [];
+ foreach ($lines as $index => $line) {
+ if (empty(trim($line))) {
+ continue;
+ }
+
+ // Parse the line.
+ [$language, $label] = array_map('trim', explode('|', $line));
+
+ $languages[] = [
+ 'label' => $label,
+ 'language' => $language,
+ ];
+ }
+ return [$languages, $unparseable_lines];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ $this->configuration['languages'] = $form_state->getValue('languages');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ return [
+ 'languages' => [
+ [ 'language' => 'plaintext', 'label' => 'Plain text' ],
+ [ 'language' => 'c', 'label' => 'C' ],
+ [ 'language' => 'cs', 'label' => 'C#' ],
+ [ 'language' => 'cpp', 'label' => 'C++' ],
+ [ 'language' => 'css', 'label' => 'CSS' ],
+ [ 'language' => 'diff', 'label' => 'Diff' ],
+ [ 'language' => 'html', 'label' => 'HTML' ],
+ [ 'language' => 'java', 'label' => 'Java' ],
+ [ 'language' => 'javascript', 'label' => 'JavaScript' ],
+ [ 'language' => 'php', 'label' => 'PHP' ],
+ [ 'language' => 'python', 'label' => 'Python' ],
+ [ 'language' => 'ruby', 'label' => 'Ruby' ],
+ [ 'language' => 'typescript', 'label' => 'TypeScript' ],
+ [ 'language' => 'xml', 'label' => 'XML' ],
+ ],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getElementsSubset(): array {
+ return array_column($this->configuration['languages'], 'language');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor): array {
+ return [
+ 'codeBlock' => [
+ 'languages' => $this->configuration['languages'],
+ ],
+ ];
+ }
+
+}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment