From e02a8b80df08492c83abe6376c370e311f6f1176 Mon Sep 17 00:00:00 2001 From: Andrew Quinn Date: Sun, 12 Jul 2026 19:38:20 -0400 Subject: [PATCH 1/3] Add configurable code blocks and inline code Adds `codeBlocks` and `inlineCode` options (both default `true`), configurable separately via preset or element attribute, following the same shape as the existing `richText` / `markdown` / `attachments` options. When disabled: - Insertion is removed from the toolbar and Markdown. The shared code button routes to whichever feature is still enabled and is hidden only when both are off. - Existing code blocks and inline code are reduced to plain text on load and paste via node transforms. The code nodes stay registered so the highlight, format-escape, and Trix conversion extensions keep working. Documented in home/docs/configuration.md. Covered by JS unit, Playwright, and Capybara round-trip tests. --- app/assets/stylesheets/lexxy-editor.css | 4 + home/docs/configuration.md | 2 + src/config/lexxy.js | 2 + src/editor/command_dispatcher.js | 14 ++- src/elements/editor.js | 61 ++++++++-- test/browser/fixtures/code-all-false.html | 24 ++++ test/browser/fixtures/code-blocks-false.html | 24 ++++ test/browser/fixtures/inline-code-false.html | 24 ++++ .../tests/formatting/code_disabled.test.js | 109 ++++++++++++++++++ test/dummy/app/views/posts/_form.html.erb | 2 + .../unit/editor/code_configuration.test.js | 52 +++++++++ test/system/code_disabled_test.rb | 50 ++++++++ 12 files changed, 357 insertions(+), 11 deletions(-) create mode 100644 test/browser/fixtures/code-all-false.html create mode 100644 test/browser/fixtures/code-blocks-false.html create mode 100644 test/browser/fixtures/inline-code-false.html create mode 100644 test/browser/tests/formatting/code_disabled.test.js create mode 100644 test/javascript/unit/editor/code_configuration.test.js create mode 100644 test/system/code_disabled_test.rb diff --git a/app/assets/stylesheets/lexxy-editor.css b/app/assets/stylesheets/lexxy-editor.css index 8b1f88966..7bfdb5086 100644 --- a/app/assets/stylesheets/lexxy-editor.css +++ b/app/assets/stylesheets/lexxy-editor.css @@ -479,6 +479,10 @@ display: none; } + &[data-code-blocks="false"][data-inline-code="false"] button[name="code"] { + display: none; + } + &[data-upload="file"] button[name="image"] { display: none; } diff --git a/home/docs/configuration.md b/home/docs/configuration.md index 54ec50985..3abb08412 100644 --- a/home/docs/configuration.md +++ b/home/docs/configuration.md @@ -46,6 +46,8 @@ Editors support the following options, configurable using presets and element at - `toolbar`: Pass `false` to disable the toolbar entirely, pass the ID of a `` element to use as an external toolbar, or pass an object to configure individual toolbar buttons. By default, the toolbar is bootstrapped and displayed above the editor. - `toolbar.upload`: Control which upload button(s) appear in the toolbar. Accepts `"file"`, `"image"`, or `"both"` (default). The image button restricts the file picker to images and videos (`accept="image/*,video/*"`), which triggers the native photo/video picker on iOS and Android. The file button opens an unrestricted file picker. - `attachments`: Pass `false` to disable attachments completely. By default, attachments are supported, including paste and drag & drop support. For finer-grained control — keeping attachments enabled while restricting which content types are accepted — use `permittedAttachmentTypes`. +- `codeBlocks`: Pass `false` to disable code blocks. Insertion via the toolbar and Markdown is removed, and any existing `
` code is reduced to plain text when loaded. By default, code blocks are enabled.
+- `inlineCode`: Pass `false` to disable inline `code`. Insertion via the toolbar and Markdown is removed, and any existing inline `` is reduced to plain text when loaded. By default, inline code is enabled.
 - `markdown`: Pass `false` to disable Markdown support.
 - `multiLine`: Pass `false` to force single line editing.
 - `permittedAttachmentTypes`: Restrict the editor to a specific allowlist of attachment content types. Unset (the default) permits any content type. Example: ``.
diff --git a/src/config/lexxy.js b/src/config/lexxy.js
index 1056f3669..06eb3c9b2 100644
--- a/src/config/lexxy.js
+++ b/src/config/lexxy.js
@@ -11,6 +11,8 @@ const global = new Configuration({
 const presets = new Configuration({
   default: {
     attachments: true,
+    codeBlocks: true,
+    inlineCode: true,
     markdown: true,
     multiLine: true,
     permittedAttachmentTypes: null,
diff --git a/src/editor/command_dispatcher.js b/src/editor/command_dispatcher.js
index 032fd13ea..dcca576a6 100644
--- a/src/editor/command_dispatcher.js
+++ b/src/editor/command_dispatcher.js
@@ -157,13 +157,23 @@ export class CommandDispatcher {
   }
 
   dispatchInsertCodeBlock() {
-    if (this.selection.hasSelectedWordsInSingleLine) {
+    if (this.#shouldToggleInlineCode()) {
       this.#toggleInlineCode()
-    } else {
+    } else if (this.editorElement.supportsCodeBlocks) {
       this.contents.toggleCodeBlock()
     }
   }
 
+  #shouldToggleInlineCode() {
+    if (!this.editorElement.supportsInlineCode) {
+      return false
+    } else if (!this.editorElement.supportsCodeBlocks) {
+      return true
+    } else {
+      return this.selection.hasSelectedWordsInSingleLine
+    }
+  }
+
   #toggleInlineCode() {
     const selection = $getSelection()
     if (!$isRangeSelection(selection)) return
diff --git a/src/elements/editor.js b/src/elements/editor.js
index bd23c32b4..d71b98c7b 100644
--- a/src/elements/editor.js
+++ b/src/elements/editor.js
@@ -1,4 +1,4 @@
-import { $addUpdateTag, $createParagraphNode, $getRoot, $getSelection, $hasUpdateTag, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, $onUpdate, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, CLEAR_HISTORY_COMMAND, COMMAND_PRIORITY_NORMAL, KEY_ENTER_COMMAND, PASTE_TAG, SKIP_DOM_SELECTION_TAG, TextNode } from "lexical"
+import { $addUpdateTag, $createParagraphNode, $createTextNode, $getRoot, $getSelection, $hasUpdateTag, $isElementNode, $isLineBreakNode, $isRangeSelection, $isTextNode, $onUpdate, CAN_REDO_COMMAND, CAN_UNDO_COMMAND, CLEAR_HISTORY_COMMAND, COMMAND_PRIORITY_NORMAL, KEY_ENTER_COMMAND, PASTE_TAG, SKIP_DOM_SELECTION_TAG, TextNode } from "lexical"
 import { buildEditorFromExtensions } from "@lexical/extension"
 import { ListItemNode, ListNode, registerList } from "@lexical/list"
 import { AutoLinkNode, LinkNode } from "@lexical/link"
@@ -9,7 +9,7 @@ import { $generateHtmlFromNodes, $generateNodesFromDOM as $generateLexicalNodesF
 import { filterDisallowedAttachmentNodes } from "../helpers/attachment_filter_helper"
 import { $convertInlineImageDataURIs } from "../helpers/inline_image_uri_helper"
 import { CodeHighlightNode, CodeNode } from "@lexical/code"
-import { TRANSFORMERS, registerMarkdownShortcuts } from "@lexical/markdown"
+import { CODE, INLINE_CODE, TRANSFORMERS, registerMarkdownShortcuts } from "@lexical/markdown"
 import { HORIZONTAL_DIVIDER } from "../editor/markdown/horizontal_divider_transformer"
 import { registerMarkdownLeadingTagHandler } from "../editor/markdown/leading_tag_handler"
 
@@ -51,9 +51,9 @@ import { nextFrame } from "../helpers/timing_helper.js"
 export class LexicalEditorElement extends HTMLElement {
   static formAssociated = true
   static debug = false
-  static commands = [ "bold", "italic", "strikethrough" ]
+  static commands = ["bold", "italic", "strikethrough"]
 
-  static observedAttributes = [ "autocapitalize", "connected", "required" ]
+  static observedAttributes = ["autocapitalize", "connected", "required"]
 
   #initialValue = ""
   #previousInternalFormValue = null
@@ -248,7 +248,7 @@ export class LexicalEditorElement extends HTMLElement {
   }
 
   get isEmpty() {
-    return [ "


", "

", "" ].includes(this.value.trim()) + return ["


", "

", ""].includes(this.value.trim()) } get isBlank() { @@ -267,6 +267,14 @@ export class LexicalEditorElement extends HTMLElement { return this.config.get("attachments") } + get supportsCodeBlocks() { + return this.supportsRichText && this.config.get("codeBlocks") + } + + get supportsInlineCode() { + return this.supportsRichText && this.config.get("inlineCode") + } + get supportsMarkdown() { return this.supportsRichText && this.config.get("markdown") } @@ -425,7 +433,7 @@ export class LexicalEditorElement extends HTMLElement { theme: theme, nodes: this.#lexicalNodes, html: { - export: new Map([ [ TextNode, exportTextNodeDOM ], [ CodeHighlightNode, exportTextNodeDOM ] ]) + export: new Map([[TextNode, exportTextNodeDOM], [CodeHighlightNode, exportTextNodeDOM]]) }, $initialEditorState: (editor) => { this.#configureSanitizer(editor) @@ -447,7 +455,7 @@ export class LexicalEditorElement extends HTMLElement { } get #lexicalNodes() { - const nodes = [ CustomActionTextAttachmentNode ] + const nodes = [CustomActionTextAttachmentNode] if (this.supportsRichText) { nodes.push( @@ -523,7 +531,7 @@ export class LexicalEditorElement extends HTMLElement { this.#setEditorHtml(initialHtml, { editor }) } - #setEditorHtml(html, { editor = this.editor } = { }) { + #setEditorHtml(html, { editor = this.editor } = {}) { $getRoot() .clear() .selectEnd() @@ -606,8 +614,10 @@ export class LexicalEditorElement extends HTMLElement { ) this.#registerTableComponents() this.#registerCodeLanguagePicker() + if (!this.supportsCodeBlocks) registered.push(this.#registerCodeBlockStripper()) + if (!this.supportsInlineCode) registered.push(this.#registerInlineCodeStripper()) if (this.supportsMarkdown) { - const transformers = [ ...TRANSFORMERS, HORIZONTAL_DIVIDER ] + const transformers = this.#markdownTransformers() registered.push( registerMarkdownShortcuts(this.editor, transformers), registerMarkdownLeadingTagHandler(this.editor, transformers) @@ -620,6 +630,37 @@ export class LexicalEditorElement extends HTMLElement { this.#listeners.track(...registered) } + #markdownTransformers() { + const excluded = new Set() + if (!this.supportsCodeBlocks) excluded.add(CODE) + if (!this.supportsInlineCode) excluded.add(INLINE_CODE) + + return [...TRANSFORMERS, HORIZONTAL_DIVIDER].filter(transformer => !excluded.has(transformer)) + } + + // Code nodes stay registered even when code blocks are disabled + #registerCodeBlockStripper() { + return this.editor.registerNodeTransform(CodeNode, (node) => { + const paragraphs = node.getTextContent().split("\n").map((line) => { + const paragraph = $createParagraphNode() + if (line.length > 0) paragraph.append($createTextNode(line)) + return paragraph + }) + + paragraphs.forEach((paragraph) => node.insertBefore(paragraph)) + node.remove() + }) + } + + // Inline code is a text format rather than a node, so strip the format from + // any imported or pasted content. Runs on load and paste; a no-op for + // unformatted text. + #registerInlineCodeStripper() { + return this.editor.registerNodeTransform(TextNode, (node) => { + if (node.hasFormat("code")) node.toggleFormat("code") + }) + } + #registerTableComponents() { let tableTools = this.querySelector("lexxy-table-tools") tableTools ??= createElement("lexxy-table-tools") @@ -743,6 +784,8 @@ export class LexicalEditorElement extends HTMLElement { const toolbar = createElement("lexxy-toolbar") toolbar.innerHTML = LexicalToolbar.defaultTemplate toolbar.setAttribute("data-attachments", this.supportsAttachments) // Drives toolbar CSS styles + toolbar.setAttribute("data-code-blocks", this.supportsCodeBlocks) + toolbar.setAttribute("data-inline-code", this.supportsInlineCode) toolbar.configure(this.config.get("toolbar")) this.prepend(toolbar) return toolbar diff --git a/test/browser/fixtures/code-all-false.html b/test/browser/fixtures/code-all-false.html new file mode 100644 index 000000000..559191984 --- /dev/null +++ b/test/browser/fixtures/code-all-false.html @@ -0,0 +1,24 @@ + + + + + + Lexxy Test — Code Fully Disabled + + + +
+
+ +
+ +
+ +
+ +
+
+ + + + diff --git a/test/browser/fixtures/code-blocks-false.html b/test/browser/fixtures/code-blocks-false.html new file mode 100644 index 000000000..a9c9342da --- /dev/null +++ b/test/browser/fixtures/code-blocks-false.html @@ -0,0 +1,24 @@ + + + + + + Lexxy Test — Code Blocks Disabled + + + +
+
+ +
+ +
+ +
+ +
+
+ + + + diff --git a/test/browser/fixtures/inline-code-false.html b/test/browser/fixtures/inline-code-false.html new file mode 100644 index 000000000..4fb2e6824 --- /dev/null +++ b/test/browser/fixtures/inline-code-false.html @@ -0,0 +1,24 @@ + + + + + + Lexxy Test — Inline Code Disabled + + + +
+
+ +
+ +
+ +
+ +
+
+ + + + diff --git a/test/browser/tests/formatting/code_disabled.test.js b/test/browser/tests/formatting/code_disabled.test.js new file mode 100644 index 000000000..792784740 --- /dev/null +++ b/test/browser/tests/formatting/code_disabled.test.js @@ -0,0 +1,109 @@ +import { test } from "../../test_helper.js" +import { expect } from "@playwright/test" +import { assertEditorHtml, startMonitoringConsole } from "../../helpers/assertions.js" +import { clickToolbarButton } from "../../helpers/toolbar.js" + +const CODE_BLOCK_HTML = '
alpha beta

After

' +const INLINE_CODE_HTML = "

Hello world again

" + +test.describe("Code blocks disabled, inline code enabled", () => { + test.beforeEach(async ({ page, editor }) => { + await page.goto("/code-blocks-false.html") + await editor.waitForConnected() + await page.waitForSelector("lexxy-toolbar[connected]") + }) + + test("the shared code toolbar button is still visible", async ({ page }) => { + await expect(page.locator("lexxy-toolbar button[name='code']")).toBeVisible() + }) + + test("the toolbar button applies inline code instead of a code block", async ({ page, editor }) => { + await editor.setValue("

hello

") + await editor.select("hello") + + await clickToolbarButton(page, "insertCodeBlock") + + await assertEditorHtml(editor, "

hello

") + await expect(editor.content.locator("pre")).toHaveCount(0) + }) + + test("typing a Markdown fence does not create a code block", async ({ editor }) => { + await editor.click() + await editor.send("```") + await editor.send("Enter") + await editor.flush() + + await expect(editor.content.locator("pre")).toHaveCount(0) + }) + + test("a loaded code block is reduced to plain text", async ({ editor }) => { + await editor.setValue(CODE_BLOCK_HTML) + + await expect(editor.content.locator("code")).toHaveCount(0) + const value = await editor.value() + expect(value).not.toContain(" { + test.beforeEach(async ({ page, editor }) => { + await page.goto("/inline-code-false.html") + await editor.waitForConnected() + await page.waitForSelector("lexxy-toolbar[connected]") + }) + + test("the shared code toolbar button is still visible", async ({ page }) => { + await expect(page.locator("lexxy-toolbar button[name='code']")).toBeVisible() + }) + + test("the toolbar button inserts a code block instead of inline code", async ({ page, editor }) => { + await editor.setValue("

hello

") + await editor.select("hello") + + await clickToolbarButton(page, "insertCodeBlock") + + await expect.poll(() => editor.value()).toContain(" { + await editor.click() + await editor.send("`hello`") + await editor.flush() + + await expect(editor.content.locator("code")).toHaveCount(0) + expect(await editor.plainTextValue()).toContain("hello") + }) + + test("loaded inline code is reduced to plain text", async ({ editor }) => { + await editor.setValue(INLINE_CODE_HTML) + + await expect(editor.content.locator("code")).toHaveCount(0) + const value = await editor.value() + expect(value).not.toContain(" { + test("the shared code toolbar button is hidden", async ({ page, editor }) => { + await page.goto("/code-all-false.html") + await editor.waitForConnected() + await page.waitForSelector("lexxy-toolbar[connected]") + + await expect(page.locator("lexxy-toolbar button[name='code']")).toBeHidden() + }) + + test("the editor connects without console errors", async ({ page, editor }) => { + startMonitoringConsole(page) + + await page.goto("/code-all-false.html") + await editor.waitForConnected() + + expect(page).toHaveNoErrors() + }) +}) diff --git a/test/dummy/app/views/posts/_form.html.erb b/test/dummy/app/views/posts/_form.html.erb index 4a50c0e0a..f5ad4b4f2 100644 --- a/test/dummy/app/views/posts/_form.html.erb +++ b/test/dummy/app/views/posts/_form.html.erb @@ -48,6 +48,8 @@ <%= form.rich_text_area :body, placeholder: "Write something...", autofocus: params[:autofocus] ? "true" : nil, attachments: (params[:attachments_disabled] != "true"), + "code-blocks": params[:code_blocks_disabled] ? "false" : nil, + "inline-code": params[:inline_code_disabled] ? "false" : nil, markdown: params[:markdown_disabled] ? "false" : nil, "single-line": params[:multi_line_disabled] ? "true" : nil, "rich-text": params[:rich_text_disabled] ? "false" : nil, diff --git a/test/javascript/unit/editor/code_configuration.test.js b/test/javascript/unit/editor/code_configuration.test.js new file mode 100644 index 000000000..1de4fc25d --- /dev/null +++ b/test/javascript/unit/editor/code_configuration.test.js @@ -0,0 +1,52 @@ +import { expect, test } from "vitest" +import { createElement } from "../helpers/dom_helper" +import EditorConfiguration from "src/editor/configuration" +import { configure } from "src/index" + +configure({ + default: { + codeBlocks: true, + inlineCode: true + }, + noCode: { + codeBlocks: false, + inlineCode: false + }, + fallbackToDefault: { + } +}) + +test("uses default code options", () => { + const element = createElement("") + const config = new EditorConfiguration(element) + expect(config.get("codeBlocks")).toBe(true) + expect(config.get("inlineCode")).toBe(true) +}) + +test("uses preset code options", () => { + const element = createElement("") + const config = new EditorConfiguration(element) + expect(config.get("codeBlocks")).toBe(false) + expect(config.get("inlineCode")).toBe(false) +}) + +test("overrides code blocks via element attribute", () => { + const element = createElement(``) + const config = new EditorConfiguration(element) + expect(config.get("codeBlocks")).toBe(false) + expect(config.get("inlineCode")).toBe(true) +}) + +test("overrides inline code via element attribute", () => { + const element = createElement(``) + const config = new EditorConfiguration(element) + expect(config.get("inlineCode")).toBe(false) + expect(config.get("codeBlocks")).toBe(true) +}) + +test("preset falls back to default code options", () => { + const element = createElement("") + const config = new EditorConfiguration(element) + expect(config.get("codeBlocks")).toBe(true) + expect(config.get("inlineCode")).toBe(true) +}) diff --git a/test/system/code_disabled_test.rb b/test/system/code_disabled_test.rb new file mode 100644 index 000000000..bfcf5f5a9 --- /dev/null +++ b/test/system/code_disabled_test.rb @@ -0,0 +1,50 @@ +require "application_system_test_case" + +class CodeDisabledTest < ApplicationSystemTestCase + test "a saved code block is reduced to plain text on load and round-trips without one" do + visit edit_post_path(posts(:empty), code_blocks_disabled: true) + wait_for_editor + + find_editor.value = '
alpha beta

After

' + + assert_no_selector "lexxy-editor pre" + assert_text "alpha beta" + assert_text "After" + + click_on "Update Post" + + within "article.post" do + assert_no_selector "pre" + assert_text "alpha beta" + assert_text "After" + end + + click_on "Edit this post" + wait_for_editor + assert_no_match(/
Date: Mon, 13 Jul 2026 20:34:54 -0400
Subject: [PATCH 2/3] Update array-bracket spacing to satisfy lint

---
 src/elements/editor.js | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/elements/editor.js b/src/elements/editor.js
index d71b98c7b..9f03e9128 100644
--- a/src/elements/editor.js
+++ b/src/elements/editor.js
@@ -51,9 +51,9 @@ import { nextFrame } from "../helpers/timing_helper.js"
 export class LexicalEditorElement extends HTMLElement {
   static formAssociated = true
   static debug = false
-  static commands = ["bold", "italic", "strikethrough"]
+  static commands = [ "bold", "italic", "strikethrough" ]
 
-  static observedAttributes = ["autocapitalize", "connected", "required"]
+  static observedAttributes = [ "autocapitalize", "connected", "required" ]
 
   #initialValue = ""
   #previousInternalFormValue = null
@@ -248,7 +248,7 @@ export class LexicalEditorElement extends HTMLElement {
   }
 
   get isEmpty() {
-    return ["


", "

", ""].includes(this.value.trim()) + return [ "


", "

", "" ].includes(this.value.trim()) } get isBlank() { @@ -433,7 +433,7 @@ export class LexicalEditorElement extends HTMLElement { theme: theme, nodes: this.#lexicalNodes, html: { - export: new Map([[TextNode, exportTextNodeDOM], [CodeHighlightNode, exportTextNodeDOM]]) + export: new Map([ [ TextNode, exportTextNodeDOM ], [ CodeHighlightNode, exportTextNodeDOM ] ]) }, $initialEditorState: (editor) => { this.#configureSanitizer(editor) @@ -455,7 +455,7 @@ export class LexicalEditorElement extends HTMLElement { } get #lexicalNodes() { - const nodes = [CustomActionTextAttachmentNode] + const nodes = [ CustomActionTextAttachmentNode ] if (this.supportsRichText) { nodes.push( @@ -635,7 +635,7 @@ export class LexicalEditorElement extends HTMLElement { if (!this.supportsCodeBlocks) excluded.add(CODE) if (!this.supportsInlineCode) excluded.add(INLINE_CODE) - return [...TRANSFORMERS, HORIZONTAL_DIVIDER].filter(transformer => !excluded.has(transformer)) + return [ ...TRANSFORMERS, HORIZONTAL_DIVIDER ].filter(transformer => !excluded.has(transformer)) } // Code nodes stay registered even when code blocks are disabled From 83b236d61b697972e33bdc02b92f2abf440ca7dc Mon Sep 17 00:00:00 2001 From: Andrew Quinn Date: Mon, 13 Jul 2026 20:49:54 -0400 Subject: [PATCH 3/3] update documentation to better clarify API --- home/docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/home/docs/configuration.md b/home/docs/configuration.md index 3abb08412..2b2d37909 100644 --- a/home/docs/configuration.md +++ b/home/docs/configuration.md @@ -46,8 +46,8 @@ Editors support the following options, configurable using presets and element at - `toolbar`: Pass `false` to disable the toolbar entirely, pass the ID of a `` element to use as an external toolbar, or pass an object to configure individual toolbar buttons. By default, the toolbar is bootstrapped and displayed above the editor. - `toolbar.upload`: Control which upload button(s) appear in the toolbar. Accepts `"file"`, `"image"`, or `"both"` (default). The image button restricts the file picker to images and videos (`accept="image/*,video/*"`), which triggers the native photo/video picker on iOS and Android. The file button opens an unrestricted file picker. - `attachments`: Pass `false` to disable attachments completely. By default, attachments are supported, including paste and drag & drop support. For finer-grained control — keeping attachments enabled while restricting which content types are accepted — use `permittedAttachmentTypes`. -- `codeBlocks`: Pass `false` to disable code blocks. Insertion via the toolbar and Markdown is removed, and any existing `
` code is reduced to plain text when loaded. By default, code blocks are enabled.
-- `inlineCode`: Pass `false` to disable inline `code`. Insertion via the toolbar and Markdown is removed, and any existing inline `` is reduced to plain text when loaded. By default, inline code is enabled.
+- `codeBlocks`: Pass `false` to disable code blocks. Markdown insertion is removed, and any existing `
` code is reduced to plain text when loaded. The shared toolbar code button remains if `inlineCode` is enabled. By default, code blocks are enabled.
+- `inlineCode`: Pass `false` to disable inline `code`. Markdown insertion is removed, and any existing inline `` is reduced to plain text when loaded. The shared toolbar code button remains if `codeBlocks` is enabled. By default, inline code is enabled.
 - `markdown`: Pass `false` to disable Markdown support.
 - `multiLine`: Pass `false` to force single line editing.
 - `permittedAttachmentTypes`: Restrict the editor to a specific allowlist of attachment content types. Unset (the default) permits any content type. Example: ``.