From 5deb8737e077ba95cea4712b220e4d95532a26d2 Mon Sep 17 00:00:00 2001 From: Mark Michaelis Date: Fri, 6 Oct 2023 12:44:49 +0200 Subject: [PATCH] fix: Fix issues detected by integration test --- .../src/rules/BBCodeCode.ts | 47 +++++++++++++++++-- .../src/rules/BBCodeList.ts | 2 +- .../src/rules/BBCodeTableSection.ts | 16 +++++-- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeCode.ts b/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeCode.ts index 28acaae362..65ab7beae1 100644 --- a/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeCode.ts +++ b/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeCode.ts @@ -2,6 +2,46 @@ import { BBCodeProcessingRule } from "./BBCodeProcessingRule"; import { removeLeadingAndTrailingNewlines } from "../BBCodeUtils"; const escapeLanguage = (language: string): string => language.replace(/([\][])/g, "\\$1"); +const ckeditorCodeBlockLanguageClassPrefix = "language-"; + +/** + * Determines the language as it is set in CKEditor's data view for a given + * code block. The language is attached as class to the nested `` + * element, like `language-css`. For plain text, the class added is + * `language-plaintext`. Classes like this may be ignored by optional + * parameter `forceUnset`. + * + * Typical CKEditor Code Blocks: + * + * ```xml + *
TEXT
+ *
CSS
+ * ``` + * + * @param element - element to try to determine language for + * @param forceUnset - languages that enforce `undefined` as return value + * @returns the detected language or `undefined` if it cannot be determined or + * is enforced to signal _unset_. + */ +const determineLanguage = (element: HTMLPreElement, forceUnset = ["plaintext"]): string | undefined => { + const { firstElementChild } = element; + if (!(firstElementChild instanceof HTMLElement)) { + return; + } + + const languageCls = Array.from(firstElementChild.classList).find((cls) => + cls.startsWith(ckeditorCodeBlockLanguageClassPrefix), + ); + if (!languageCls) { + return; + } + + const language = languageCls.replace(ckeditorCodeBlockLanguageClassPrefix, ""); + if (!language || forceUnset.includes(language)) { + return; + } + return language; +}; /** * Maps `
` to `[code]`.
@@ -13,13 +53,10 @@ export class BBCodeCode implements BBCodeProcessingRule {
     if (!(element instanceof HTMLPreElement)) {
       return;
     }
-    // CKEditor 5 encodes the language as `language` in dataset.
-    const {
-      dataset: { language },
-    } = element;
+    const language = determineLanguage(element);
     const trimmed = removeLeadingAndTrailingNewlines(content);
     if (language) {
-      return `[code=${escapeLanguage(language)}]${trimmed}\n[/code]\n`;
+      return `[code=${escapeLanguage(language)}]\n${trimmed}\n[/code]\n`;
     }
     return `[code]\n${trimmed}\n[/code]\n`;
   }
diff --git a/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeList.ts b/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeList.ts
index b2886678a3..3cd9597be0 100644
--- a/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeList.ts
+++ b/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeList.ts
@@ -11,7 +11,7 @@ export class BBCodeList implements BBCodeProcessingRule {
       return `[list]\n${content.trim()}\n[/list]\n`;
     }
     if (element instanceof HTMLOListElement) {
-      return `[list=${element.type}]\n${content.trim()}\n[/list]\n`;
+      return `[list=${element.type || "1"}]\n${content.trim()}\n[/list]\n`;
     }
   }
 }
diff --git a/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeTableSection.ts b/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeTableSection.ts
index 9daddc08be..481e8644e9 100644
--- a/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeTableSection.ts
+++ b/packages/ckeditor5-coremedia-bbcode/src/rules/BBCodeTableSection.ts
@@ -1,4 +1,5 @@
 import { BBCodeProcessingRule } from "./BBCodeProcessingRule";
+import { isText } from "@coremedia/ckeditor5-dom-support";
 
 /**
  * Maps ``, ``, `` to corresponding BBCode.
@@ -10,19 +11,26 @@ export class BBCodeTableSection implements BBCodeProcessingRule {
     if (!(element instanceof HTMLTableSectionElement)) {
       return;
     }
-    const { tagName } = element;
+    const { nextSibling, tagName } = element;
+
+    // Minor Pretty-Print Optimization to not pile up newlines when a
+    // corresponding newline already exists in HTML.
+    const finalNewline = isText(nextSibling) && nextSibling?.textContent?.startsWith("\n") ? "" : "\n";
+
     const normalizedTagName = tagName.toLowerCase();
+
     switch (normalizedTagName) {
       case "thead":
-        return `[thead]\n${content.trim()}\n[/thead]\n`;
+        return `[thead]\n${content.trim()}\n[/thead]${finalNewline}`;
       case "tfoot":
-        return `[tfoot]\n${content.trim()}\n[/tfoot]\n`;
+        return `[tfoot]\n${content.trim()}\n[/tfoot]${finalNewline}`;
       case "tbody":
         // More detailed handling below.
         break;
       default:
         return;
     }
+
     // We now know that we have a `` element. Apply some
     // simplification to the resulting BBCode, when `` is the only
     // table section element.
@@ -32,7 +40,7 @@ export class BBCodeTableSection implements BBCodeProcessingRule {
       // within the table. Thus, no need for surrounding `[tbody]`.
       return content.trim();
     }
-    return `[tbody]\n${content.trim()}\n[/tbody]\n`;
+    return `[tbody]\n${content.trim()}\n[/tbody]${finalNewline}`;
   }
 }