Repository: NDLANO/editorial-frontend
Pull request #2974: Refactored definition plugin to new plugin system
Author: login: MaPoKen
Created: 2025-04-10T10:59:06Z | Merged: 2025-04-23T13:40:46Z

Skriv om definisjonsliste til nytt plugin system


Diff:
diff --git a/src/components/SlateEditor/interfaces.ts b/src/components/SlateEditor/interfaces.ts
index cb7230bf19..197eda96b4 100644
--- a/src/components/SlateEditor/interfaces.ts
+++ b/src/components/SlateEditor/interfaces.ts
@@ -30,7 +30,6 @@ import { ConceptBlockElement } from "./plugins/concept/block/interfaces";
 import { ConceptInlineElement } from "./plugins/concept/inline/interfaces";
 import { ContactBlockElement } from "./plugins/contactBlock/types";
 import { CopyrightElement } from "./plugins/copyright/types";
-import { DefinitionDescriptionElement, DefinitionListElement, DefinitionTermElement } from "./plugins/definitionList";
 import { DetailsElement } from "./plugins/details/detailsTypes";
 import { SummaryElement } from "./plugins/details/summaryTypes";
 import { DivElement } from "./plugins/div";
@@ -61,6 +60,11 @@ import {
 import { DisclaimerElement } from "./plugins/uuDisclaimer/types";
 import { BrightcoveEmbedElement } from "./plugins/video/types";
 import { RephraseElement } from "./plugins/rephrase/rephraseTypes";
+import {
+  DefinitionListElement,
+  DefinitionDescriptionElement,
+  DefinitionTermElement,
+} from "./plugins/definitionList/definitionListTypes";
 
 export type SlatePlugin = (editor: Editor) => Editor;
 
diff --git a/src/components/SlateEditor/plugins/definitionList/__tests__/normalizer-test.ts b/src/components/SlateEditor/plugins/definitionList/__tests__/normalizer-test.ts
index 846313019f..3a839cddf2 100644
--- a/src/components/SlateEditor/plugins/definitionList/__tests__/normalizer-test.ts
+++ b/src/components/SlateEditor/plugins/definitionList/__tests__/normalizer-test.ts
@@ -11,8 +11,12 @@ import { createSlate } from "@ndla/editor";
 import { learningResourcePlugins } from "../../../../../containers/ArticlePage/LearningResourcePage/components/learningResourcePlugins";
 import { TYPE_PARAGRAPH } from "../../paragraph/types";
 import { TYPE_SECTION } from "../../section/types";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
 import { anySlateElementId } from "../../../../../__tests__/vitest.setup";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "../definitionListTypes";
 
 const editor = createSlate({ plugins: learningResourcePlugins });
 
@@ -20,31 +24,31 @@ describe("definition normalizing tests", () => {
   test("should not remove any description or term", () => {
     const editorValue: Descendant[] = [
       {
-        type: TYPE_DEFINITION_LIST,
+        type: DEFINITION_LIST_ELEMENT_TYPE,
         children: [
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
         ],
       },
     ];
 
     const expectedValue: Descendant[] = [
       {
-        type: TYPE_DEFINITION_LIST,
+        type: DEFINITION_LIST_ELEMENT_TYPE,
         id: anySlateElementId,
         children: [
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
         ],
       },
     ];
@@ -55,37 +59,35 @@ describe("definition normalizing tests", () => {
   test("should only contain term and description objects", () => {
     const editorValue: Descendant[] = [
       {
-        type: TYPE_DEFINITION_LIST,
+        type: DEFINITION_LIST_ELEMENT_TYPE,
         children: [
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
           { type: TYPE_PARAGRAPH, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
         ],
       },
     ];
-
     const expectedValue: Descendant[] = [
       {
-        type: TYPE_DEFINITION_LIST,
+        type: DEFINITION_LIST_ELEMENT_TYPE,
         id: anySlateElementId,
         children: [
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-          { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+          { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
         ],
       },
     ];
+
     editor.reinitialize({ value: editorValue, shouldNormalize: true });
     expect(editor.children).toEqual(expectedValue);
   });
@@ -100,17 +102,17 @@ describe("definition normalizing tests", () => {
             children: [{ text: "" }],
           },
           {
-            type: TYPE_DEFINITION_LIST,
+            type: DEFINITION_LIST_ELEMENT_TYPE,
             children: [
-              { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-              { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
+              { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+              { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
             ],
           },
           {
-            type: TYPE_DEFINITION_LIST,
+            type: DEFINITION_LIST_ELEMENT_TYPE,
             children: [
-              { type: TYPE_DEFINITION_TERM, children: [{ text: "" }] },
-              { type: TYPE_DEFINITION_DESCRIPTION, children: [{ text: "" }] },
+              { type: DEFINITION_TERM_ELEMENT_TYPE, children: [{ text: "" }] },
+              { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, children: [{ text: "" }] },
             ],
           },
           {
@@ -132,13 +134,13 @@ describe("definition normalizing tests", () => {
             children: [{ text: "" }],
           },
           {
-            type: TYPE_DEFINITION_LIST,
+            type: DEFINITION_LIST_ELEMENT_TYPE,
             id: anySlateElementId,
             children: [
-              { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-              { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
-              { type: TYPE_DEFINITION_TERM, id: anySlateElementId, children: [{ text: "" }] },
-              { type: TYPE_DEFINITION_DESCRIPTION, id: anySlateElementId, children: [{ text: "" }] },
+              { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+              { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+              { type: DEFINITION_TERM_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
+              { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE, id: anySlateElementId, children: [{ text: "" }] },
             ],
           },
           {
diff --git a/src/components/SlateEditor/plugins/definitionList/__tests__/serializer-test.ts b/src/components/SlateEditor/plugins/definitionList/__tests__/serializer-test.ts
index 2f51591320..8ef4573752 100644
--- a/src/components/SlateEditor/plugins/definitionList/__tests__/serializer-test.ts
+++ b/src/components/SlateEditor/plugins/definitionList/__tests__/serializer-test.ts
@@ -9,21 +9,25 @@
 import { Descendant } from "slate";
 import { blockContentToEditorValue, blockContentToHTML } from "../../../../../util/articleContentConverter";
 import { TYPE_SECTION } from "../../section/types";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "../definitionListTypes";
 
 const editor: Descendant[] = [
   {
     type: TYPE_SECTION,
     children: [
       {
-        type: TYPE_DEFINITION_LIST,
+        type: DEFINITION_LIST_ELEMENT_TYPE,
         children: [
           {
-            type: TYPE_DEFINITION_TERM,
+            type: DEFINITION_TERM_ELEMENT_TYPE,
             children: [{ text: "Tester vi testesen" }],
           },
           {
-            type: TYPE_DEFINITION_DESCRIPTION,
+            type: DEFINITION_DESCRIPTION_ELEMENT_TYPE,
             children: [
               {
                 text: "En test er en test vi tester for å teste om testingen gir test resultater",
diff --git a/src/components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin.ts b/src/components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin.ts
new file mode 100644
index 0000000000..6b3e6ce7a8
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin.ts
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_DESCRIPTION_PLUGIN,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionDescriptionType,
+} from "./definitionListTypes";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+import { isDefinitionDescriptionElement } from "./queries/definitionListQueries";
+
+const normalizerConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+  previous: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+
+export const definitionDescriptionSerializer = createSerializer({
+  deserialize(el, children) {
+    const tag = el.tagName.toLowerCase();
+    if (tag !== "dd") return;
+    return slatejsx("element", { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE }, children);
+  },
+  serialize(node, children) {
+    if (!isDefinitionDescriptionElement(node)) return;
+    return createHtmlTag({ tag: "dd", children });
+  },
+});
+
+export const definitionDescriptionPlugin = createPlugin<DefinitionDescriptionType>({
+  name: DEFINITION_DESCRIPTION_PLUGIN,
+  type: DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  normalize: (editor, node, path, logger) => {
+    if (!isDefinitionDescriptionElement(node)) return false;
+    return defaultBlockNormalizer(editor, node, path, normalizerConfig, logger);
+  },
+});
diff --git a/src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts b/src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
new file mode 100644
index 0000000000..d08384a4db
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Node, Path, Range, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onTab } from "./handlers/onTab";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+import {
+  isDefinitionDescriptionElement,
+  isDefinitionListElement,
+  isDefinitionTermElement,
+} from "./queries/definitionListQueries";
+
+const normalizerConfig: NormalizerConfig = {
+  firstNode: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+  nodes: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+export const definitionListSerializer = createSerializer({
+  deserialize(el, children) {
+    const tag = el.tagName.toLowerCase();
+    if (tag !== "dl") return;
+    return slatejsx("element", { type: DEFINITION_LIST_ELEMENT_TYPE }, children);
+  },
+  serialize(node, children) {
+    if (!isDefinitionListElement(node)) return;
+    return createHtmlTag({ tag: "dl", children });
+  },
+});
+
+export const definitionListPlugin = createPlugin<DefinitionListType>({
+  name: DEFINITION_LIST_PLUGIN,
+  type: DEFINITION_LIST_ELEMENT_TYPE,
+  shortcuts: {
+    dentList: { keyCondition: isKeyHotkey("shift?+tab"), handler: onTab },
+  },
+  normalize: (editor, node, path, logger) => {
+    if (!isDefinitionListElement(node)) return false;
+    if (Path.hasPrevious(path)) {
+      const previousPath = Path.previous(path);
+      if (Editor.hasPath(editor, previousPath)) {
+        const [prevNode] = Editor.node(editor, previousPath);
+        if (isDefinitionListElement(prevNode)) {
+          logger.log("Current node and previous node is definition list, merging.");
+          Transforms.mergeNodes(editor, { at: path });
+          return true;
+        }
+      }
+    }
+    return defaultBlockNormalizer(editor, node, path, normalizerConfig, logger);
+  },
+  transform: (editor, logger) => {
+    const { insertBreak } = editor;
+
+    editor.insertBreak = () => {
+      if (!editor.selection || Range.isExpanded(editor.selection)) return insertBreak();
+      const [parentNode, parentPath] = editor.parent(editor.selection);
+
+      if (!isDefinitionTermElement(parentNode) && !isDefinitionDescriptionElement(parentNode)) return insertBreak();
+      if (Node.string(parentNode) !== "" || editor.hasVoids(parentNode)) return insertBreak();
+
+      if (parentNode.type === DEFINITION_DESCRIPTION_ELEMENT_TYPE) {
+        logger.log("Tried to enter on empty definition description, converting it to term.");
+        Transforms.setNodes(editor, { type: DEFINITION_TERM_ELEMENT_TYPE }, { at: parentPath });
+      } else {
+        logger.log("Tried to enter on empty definition term, splitting list and inserting paragraph");
+        editor.withoutNormalizing(() => {
+          Transforms.unwrapNodes(editor, { at: parentPath });
+          Transforms.liftNodes(editor, { at: parentPath });
+        });
+      }
+    };
+    return editor;
+  },
+});
diff --git a/src/components/SlateEditor/plugins/definitionList/definitionListTypes.ts b/src/components/SlateEditor/plugins/definitionList/definitionListTypes.ts
new file mode 100644
index 0000000000..b324c224f4
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/definitionListTypes.ts
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { Descendant } from "slate";
+
+export type DefinitionListType = "definition-list";
+export type DefinitionTermType = "definition-term";
+export type DefinitionDescriptionType = "definition-description";
+
+export const DEFINITION_LIST_ELEMENT_TYPE = "definition-list" as const;
+export const DEFINITION_DESCRIPTION_ELEMENT_TYPE = "definition-description" as const;
+export const DEFINITION_TERM_ELEMENT_TYPE = "definition-term" as const;
+
+export const DEFINITION_LIST_PLUGIN = "definition-list" as const;
+export const DEFINITION_DESCRIPTION_PLUGIN = "definition-description" as const;
+export const DEFINITION_TERM_PLUGIN = "definition-term" as const;
+
+export interface DefinitionListElement {
+  type: "definition-list";
+  children: Descendant[];
+}
+
+export interface DefinitionTermElement {
+  type: "definition-term";
+  children: Descendant[];
+}
+
+export interface DefinitionDescriptionElement {
+  type: "definition-description";
+  children: Descendant[];
+}
diff --git a/src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts b/src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
new file mode 100644
index 0000000000..8f87074a78
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DEFINITION_TERM_PLUGIN,
+} from "./definitionListTypes";
+import { isDefinitionTermElement } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+export const definitionTermSerializer = createSerializer({
+  deserialize(el, children) {
+    const tag = el.tagName.toLowerCase();
+    if (tag !== "dt") return;
+    return slatejsx("element", { type: DEFINITION_TERM_ELEMENT_TYPE }, children);
+  },
+  serialize(node, children) {
+    if (!isDefinitionTermElement(node)) return;
+    return createHtmlTag({ tag: "dt", children });
+  },
+});
+
+const normalizerConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+};
+
+export const definitionTermPlugin = createPlugin({
+  name: DEFINITION_TERM_PLUGIN,
+  type: DEFINITION_TERM_ELEMENT_TYPE,
+  normalize: (editor, node, path, logger) => {
+    if (!isDefinitionTermElement(node)) return false;
+    return defaultBlockNormalizer(editor, node, path, normalizerConfig, logger);
+  },
+});
diff --git a/src/components/SlateEditor/plugins/definitionList/handlers/onBackspace.ts b/src/components/SlateEditor/plugins/definitionList/handlers/onBackspace.ts
deleted file mode 100644
index 802cf9675b..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/handlers/onBackspace.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import { KeyboardEvent } from "react";
-import { Editor, Element, Transforms, Range, Point } from "slate";
-import hasNodeOfType from "../../../utils/hasNodeOfType";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
-
-const onBackspace = (
-  e: KeyboardEvent<HTMLDivElement>,
-  editor: Editor,
-  nextOnKeyDown: ((e: KeyboardEvent<HTMLDivElement>) => void) | undefined,
-) => {
-  if (!editor.selection) return nextOnKeyDown?.(e);
-  const isDefinition = hasNodeOfType(editor, TYPE_DEFINITION_LIST);
-
-  if (!isDefinition) {
-    return nextOnKeyDown?.(e);
-  }
-
-  const [selectedDefinitionItem, selectedDefinitionItemPath] = Editor.parent(editor, editor.selection.anchor.path);
-  if (selectedDefinitionItem) {
-    if (
-      Element.isElement(selectedDefinitionItem) &&
-      (selectedDefinitionItem.type === TYPE_DEFINITION_DESCRIPTION ||
-        selectedDefinitionItem.type === TYPE_DEFINITION_TERM)
-    ) {
-      if (Range.isCollapsed(editor.selection)) {
-        const [, firstItemNodePath] = Editor.node(editor, [...selectedDefinitionItemPath, 0]);
-        if (Point.equals(Range.start(editor.selection), Editor.start(editor, firstItemNodePath))) {
-          e.preventDefault();
-          return Transforms.liftNodes(editor, {
-            at: selectedDefinitionItemPath,
-          });
-        }
-      }
-    }
-  }
-
-  return nextOnKeyDown?.(e);
-};
-
-export default onBackspace;
diff --git a/src/components/SlateEditor/plugins/definitionList/handlers/onEnter.ts b/src/components/SlateEditor/plugins/definitionList/handlers/onEnter.ts
deleted file mode 100644
index bb771ff98d..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/handlers/onEnter.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import type { KeyboardEvent } from "react";
-import { Editor, Element, Path, Transforms, Node, Range, Point } from "slate";
-import { ReactEditor } from "slate-react";
-import { getEditorAncestors } from "../../toolbar/toolbarState";
-import { TYPE_DEFINITION_TERM, TYPE_DEFINITION_DESCRIPTION } from "../types";
-import { definitionDescription, definitionTerm } from "../utils/defaultBlocks";
-
-const onEnter = (
-  e: KeyboardEvent<HTMLDivElement>,
-  editor: Editor,
-  nextOnKeyDown: ((e: KeyboardEvent<HTMLDivElement>) => void) | undefined,
-) => {
-  if ((e.shiftKey && nextOnKeyDown) || (!editor.selection && nextOnKeyDown)) {
-    return nextOnKeyDown(e);
-  } else if (!editor.selection) return;
-
-  const [firstChild, secondChild] = getEditorAncestors(editor, true);
-  const selectedDefinitionItem =
-    firstChild.type === TYPE_DEFINITION_DESCRIPTION || firstChild.type === TYPE_DEFINITION_TERM
-      ? firstChild
-      : secondChild;
-
-  if (!selectedDefinitionItem) {
-    return nextOnKeyDown?.(e);
-  }
-
-  const selectedDefinitionItemPath = ReactEditor.findPath(editor, selectedDefinitionItem);
-
-  if (
-    !selectedDefinitionItem ||
-    !Element.isElement(selectedDefinitionItem) ||
-    (selectedDefinitionItem.type !== TYPE_DEFINITION_DESCRIPTION &&
-      selectedDefinitionItem.type !== TYPE_DEFINITION_TERM)
-  ) {
-    return nextOnKeyDown?.(e);
-  }
-
-  e.preventDefault();
-
-  if (Range.isExpanded(editor.selection)) {
-    Editor.deleteFragment(editor);
-  }
-
-  if (Node.string(selectedDefinitionItem) === "" && selectedDefinitionItem.children.length === 1) {
-    Editor.withoutNormalizing(editor, () => {
-      Transforms.unwrapNodes(editor, {
-        at: selectedDefinitionItemPath,
-      });
-      Transforms.liftNodes(editor, {
-        at: selectedDefinitionItemPath,
-      });
-    });
-    return;
-  }
-
-  Transforms.unsetNodes(editor, "serializeAsText", {
-    match: (node) =>
-      Element.isElement(node) && (node.type === TYPE_DEFINITION_DESCRIPTION || node.type === TYPE_DEFINITION_TERM),
-    mode: "lowest",
-  });
-
-  const nextPoint = Editor.after(editor, Range.end(editor.selection));
-  const listItemEnd = Editor.end(editor, selectedDefinitionItemPath);
-
-  if ((nextPoint && Point.equals(listItemEnd, nextPoint)) || Point.equals(listItemEnd, editor.selection.anchor)) {
-    const nextPath = Path.next(selectedDefinitionItemPath);
-    if (Element.isElement(selectedDefinitionItem) && selectedDefinitionItem.type === TYPE_DEFINITION_TERM) {
-      Transforms.insertNodes(editor, definitionTerm(), { at: nextPath });
-    } else if (
-      Element.isElement(selectedDefinitionItem) &&
-      selectedDefinitionItem.type === TYPE_DEFINITION_DESCRIPTION
-    ) {
-      Transforms.insertNodes(editor, definitionDescription(), { at: nextPath });
-    }
-    Transforms.select(editor, Editor.start(editor, nextPath));
-    return;
-  }
-
-  // Split current listItem at selection.
-  Transforms.splitNodes(editor, {
-    match: (node) =>
-      Element.isElement(node) && (node.type === TYPE_DEFINITION_TERM || node.type === TYPE_DEFINITION_DESCRIPTION),
-    mode: "lowest",
-  });
-};
-
-export default onEnter;
diff --git a/src/components/SlateEditor/plugins/definitionList/handlers/onTab.ts b/src/components/SlateEditor/plugins/definitionList/handlers/onTab.ts
new file mode 100644
index 0000000000..8dc17021e9
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/handlers/onTab.ts
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2023-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { Transforms } from "slate";
+import { ShortcutHandler } from "@ndla/editor";
+import hasNodeOfType from "../../../utils/hasNodeOfType";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "../definitionListTypes";
+import { isDefinitionTermElement, isDefinitionDescriptionElement } from "../queries/definitionListQueries";
+
+export const onTab: ShortcutHandler = (editor, event, logger) => {
+  if (!editor.selection || !hasNodeOfType(editor, DEFINITION_LIST_ELEMENT_TYPE)) return false;
+
+  const [parentNode, parentPath] = editor.parent(editor.selection);
+
+  if (event.shiftKey && isDefinitionDescriptionElement(parentNode)) {
+    event.preventDefault();
+    logger.log("Shift + Tab event on DESCRIPTION, setting type to TERM.");
+    Transforms.setNodes(editor, { type: DEFINITION_TERM_ELEMENT_TYPE }, { at: parentPath });
+    return true;
+  } else if (isDefinitionTermElement(parentNode) && !event.shiftKey) {
+    event.preventDefault();
+    logger.log("Tab event on TERM, setting type to DESCRIPTION.");
+    Transforms.setNodes(editor, { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE }, { at: parentPath });
+    return true;
+  }
+  return false;
+};
diff --git a/src/components/SlateEditor/plugins/definitionList/handlers/onTab.tsx b/src/components/SlateEditor/plugins/definitionList/handlers/onTab.tsx
deleted file mode 100644
index 9792aa123b..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/handlers/onTab.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import type { KeyboardEvent } from "react";
-import { Editor, Element, Transforms } from "slate";
-import { ReactEditor } from "slate-react";
-import getCurrentBlock from "../../../utils/getCurrentBlock";
-import hasNodeOfType from "../../../utils/hasNodeOfType";
-import { getEditorAncestors } from "../../toolbar/toolbarState";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
-
-const onTab = (
-  event: KeyboardEvent<HTMLDivElement>,
-  editor: Editor,
-  next?: (event: KeyboardEvent<HTMLDivElement>) => void,
-) => {
-  event.preventDefault();
-  const isDefinition = hasNodeOfType(editor, TYPE_DEFINITION_LIST);
-  if (!isDefinition || !editor.selection) {
-    return next?.(event);
-  }
-
-  const listEntry = getCurrentBlock(editor, TYPE_DEFINITION_LIST);
-  const listItemEntry = Editor.parent(editor, editor.selection.anchor.path);
-
-  if (!listEntry || !listItemEntry) {
-    return next?.(event);
-  }
-
-  const [currentListNode] = listEntry;
-  const [firstChild, secondChild] = getEditorAncestors(editor, true);
-
-  const selectedDefinitionItem =
-    firstChild.type === TYPE_DEFINITION_DESCRIPTION || firstChild.type === TYPE_DEFINITION_TERM
-      ? firstChild
-      : secondChild;
-
-  const selectedDefinitionItemPath = ReactEditor.findPath(editor, selectedDefinitionItem);
-
-  if (Element.isElement(currentListNode) && currentListNode.type === TYPE_DEFINITION_LIST) {
-    Editor.withoutNormalizing(editor, () => {
-      if (event.shiftKey && selectedDefinitionItem.type === TYPE_DEFINITION_DESCRIPTION) {
-        Transforms.setNodes(
-          editor,
-          {
-            type: TYPE_DEFINITION_TERM,
-            children: selectedDefinitionItem.children,
-          },
-          { at: selectedDefinitionItemPath },
-        );
-      } else if (selectedDefinitionItem.type === TYPE_DEFINITION_TERM) {
-        Transforms.setNodes(
-          editor,
-          {
-            type: TYPE_DEFINITION_DESCRIPTION,
-            children: selectedDefinitionItem.children,
-          },
-          { at: selectedDefinitionItemPath },
-        );
-      }
-    });
-  }
-};
-
-export default onTab;
diff --git a/src/components/SlateEditor/plugins/definitionList/index.tsx b/src/components/SlateEditor/plugins/definitionList/index.tsx
deleted file mode 100644
index d7e4436e8a..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/index.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import { Descendant, Editor, Element, Path, Transforms } from "slate";
-import { jsx as slatejsx } from "slate-hyperscript";
-import onBackspace from "./handlers/onBackspace";
-import onEnter from "./handlers/onEnter";
-import onTab from "./handlers/onTab";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "./types";
-import { createHtmlTag } from "../../../../util/embedTagHelpers";
-import { SlateSerializer } from "../../interfaces";
-import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
-import { KEY_BACKSPACE, KEY_ENTER, KEY_TAB } from "../../utils/keys";
-
-export interface DefinitionListElement {
-  type: "definition-list";
-  children: Descendant[];
-}
-
-export interface DefinitionTermElement {
-  type: "definition-term";
-  children: Descendant[];
-}
-
-export interface DefinitionDescriptionElement {
-  type: "definition-description";
-  children: Descendant[];
-}
-
-const normalizerDLConfig: NormalizerConfig = {
-  nodes: {
-    allowed: [TYPE_DEFINITION_TERM, TYPE_DEFINITION_DESCRIPTION],
-    defaultType: TYPE_DEFINITION_TERM,
-  },
-};
-const normalizerDTConfig: NormalizerConfig = {
-  parent: {
-    allowed: [TYPE_DEFINITION_LIST],
-  },
-};
-
-const normalizerDDConfig: NormalizerConfig = {
-  ...normalizerDTConfig,
-  previous: {
-    allowed: [TYPE_DEFINITION_TERM, TYPE_DEFINITION_DESCRIPTION],
-    defaultType: TYPE_DEFINITION_TERM,
-  },
-};
-
-export const definitionListSerializer: SlateSerializer = {
-  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
-    const tag = el.tagName.toLowerCase();
-    if (tag === "dl") {
-      return slatejsx("element", { type: TYPE_DEFINITION_LIST }, children);
-    }
-    if (tag === "dd") {
-      return slatejsx("element", { type: TYPE_DEFINITION_DESCRIPTION }, children);
-    }
-    if (tag === "dt") {
-      return slatejsx("element", { type: TYPE_DEFINITION_TERM }, children);
-    }
-  },
-  serialize(node, children) {
-    if (!Element.isElement(node)) return;
-    if (node.type === TYPE_DEFINITION_LIST) {
-      return createHtmlTag({ tag: "dl", children });
-    }
-    if (node.type === TYPE_DEFINITION_TERM) {
-      return createHtmlTag({ tag: "dt", children });
-    }
-    if (node.type === TYPE_DEFINITION_DESCRIPTION) {
-      return createHtmlTag({ tag: "dd", children });
-    }
-  },
-};
-
-export const definitionListPlugin = (editor: Editor) => {
-  const { normalizeNode: nextNormalizeNode, onKeyDown: nextOnKeyDown } = editor;
-
-  editor.onKeyDown = (e) => {
-    if (e.key === KEY_ENTER) {
-      onEnter(e, editor, nextOnKeyDown);
-    } else if (e.key === KEY_BACKSPACE) {
-      onBackspace(e, editor, nextOnKeyDown);
-    } else if (e.key === KEY_TAB) {
-      onTab(e, editor, nextOnKeyDown);
-    } else if (nextOnKeyDown) {
-      nextOnKeyDown(e);
-    }
-  };
-
-  editor.normalizeNode = (entry) => {
-    const [node, nodepath] = entry;
-
-    if (Element.isElement(node) && node.type === TYPE_DEFINITION_LIST) {
-      // Merge list with previous list if identical type
-      if (Path.hasPrevious(nodepath)) {
-        const previousPath = Path.previous(nodepath);
-        if (Editor.hasPath(editor, previousPath)) {
-          const [prevNode] = Editor.node(editor, previousPath);
-          if (Element.isElement(prevNode) && prevNode.type === TYPE_DEFINITION_LIST) {
-            return Transforms.mergeNodes(editor, {
-              at: nodepath,
-            });
-          }
-        }
-      }
-
-      if (defaultBlockNormalizer(editor, node, nodepath, normalizerDLConfig)) {
-        return;
-      }
-    } else if (Element.isElement(node) && node.type === TYPE_DEFINITION_TERM) {
-      if (defaultBlockNormalizer(editor, node, nodepath, normalizerDTConfig)) {
-        return;
-      }
-    } else if (Element.isElement(node) && node.type === TYPE_DEFINITION_DESCRIPTION) {
-      if (defaultBlockNormalizer(editor, node, nodepath, normalizerDDConfig)) {
-        return;
-      }
-    }
-    nextNormalizeNode(entry);
-  };
-  return editor;
-};
diff --git a/src/components/SlateEditor/plugins/definitionList/queries/definitionListQueries.ts b/src/components/SlateEditor/plugins/definitionList/queries/definitionListQueries.ts
new file mode 100644
index 0000000000..98b13e5376
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/queries/definitionListQueries.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { Node } from "slate";
+import { isElementOfType } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "../definitionListTypes";
+
+export const isDefinitionTermElement = (node: Node | undefined) => isElementOfType(node, DEFINITION_TERM_ELEMENT_TYPE);
+
+export const isDefinitionDescriptionElement = (node: Node | undefined) =>
+  isElementOfType(node, DEFINITION_DESCRIPTION_ELEMENT_TYPE);
+
+export const isDefinitionListElement = (node: Node | undefined) => isElementOfType(node, DEFINITION_LIST_ELEMENT_TYPE);
diff --git a/src/components/SlateEditor/plugins/definitionList/render.tsx b/src/components/SlateEditor/plugins/definitionList/render.tsx
index 05e82f29c0..2999628518 100644
--- a/src/components/SlateEditor/plugins/definitionList/render.tsx
+++ b/src/components/SlateEditor/plugins/definitionList/render.tsx
@@ -8,16 +8,20 @@
 
 import { Editor } from "slate";
 import { DefinitionList } from "@ndla/primitives";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "./types";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "./definitionListTypes";
 
 export const definitionListRenderer = (editor: Editor) => {
   const { renderElement } = editor;
   editor.renderElement = ({ attributes, children, element }) => {
-    if (element.type === TYPE_DEFINITION_LIST) {
+    if (element.type === DEFINITION_LIST_ELEMENT_TYPE) {
       return <DefinitionList {...attributes}>{children}</DefinitionList>;
-    } else if (element.type === TYPE_DEFINITION_DESCRIPTION) {
+    } else if (element.type === DEFINITION_DESCRIPTION_ELEMENT_TYPE) {
       return <dd {...attributes}>{children}</dd>;
-    } else if (element.type === TYPE_DEFINITION_TERM) {
+    } else if (element.type === DEFINITION_TERM_ELEMENT_TYPE) {
       return <dt {...attributes}>{children}</dt>;
     } else return renderElement?.({ attributes, children, element });
   };
diff --git a/src/components/SlateEditor/plugins/definitionList/transforms/toggleDefinitionList.ts b/src/components/SlateEditor/plugins/definitionList/transforms/toggleDefinitionList.ts
new file mode 100644
index 0000000000..26714134f2
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/transforms/toggleDefinitionList.ts
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { Editor, Transforms, Range, Element } from "slate";
+import { firstTextBlockElement } from "../../../utils/normalizationHelpers";
+import { DEFINITION_LIST_ELEMENT_TYPE } from "../definitionListTypes";
+import {
+  isDefinitionDescriptionElement,
+  isDefinitionTermElement,
+  isDefinitionListElement,
+} from "../queries/definitionListQueries";
+
+const isOnlySelectionOfDefinitionList = (editor: Editor) => {
+  let hasListItems = false;
+
+  const nodes = editor.nodes({ match: (n) => isDefinitionDescriptionElement(n) || isDefinitionTermElement(n) });
+  for (const [, path] of nodes) {
+    const [parentNode] = Editor.parent(editor, path);
+    if (isDefinitionListElement(parentNode)) {
+      hasListItems = true;
+      continue;
+    }
+    return false;
+  }
+  return hasListItems;
+};
+
+// TODO: This doesn't work perfectly when combined with regular lists.
+export const toggleDefinitionList = (editor: Editor) => {
+  if (!Range.isRange(editor.selection)) {
+    return;
+  }
+  const isSelected = isOnlySelectionOfDefinitionList(editor);
+
+  if (isSelected) {
+    Transforms.liftNodes(editor, {
+      match: (node) => isDefinitionDescriptionElement(node) || isDefinitionTermElement(node),
+      mode: "all",
+    });
+  } else {
+    Transforms.setNodes(
+      editor,
+      { type: DEFINITION_LIST_ELEMENT_TYPE },
+      {
+        match: (node) => Element.isElement(node) && firstTextBlockElement.includes(node.type),
+        at: Editor.unhangRange(editor, editor.selection),
+        mode: "lowest",
+      },
+    );
+  }
+};
diff --git a/src/components/SlateEditor/plugins/definitionList/types.ts b/src/components/SlateEditor/plugins/definitionList/types.ts
deleted file mode 100644
index 7240092141..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/types.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-export const TYPE_DEFINITION_LIST = "definition-list";
-export const TYPE_DEFINITION_DESCRIPTION = "definition-description";
-export const TYPE_DEFINITION_TERM = "definition-term";
diff --git a/src/components/SlateEditor/plugins/definitionList/utils.ts b/src/components/SlateEditor/plugins/definitionList/utils.ts
new file mode 100644
index 0000000000..6484e14433
--- /dev/null
+++ b/src/components/SlateEditor/plugins/definitionList/utils.ts
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "./definitionListTypes";
+
+export const defaultDefinitionTermBlock = () =>
+  slatejsx("element", { type: DEFINITION_TERM_ELEMENT_TYPE }, [{ text: "" }]);
+
+export const defaultDefinitionDescriptionBlock = () =>
+  slatejsx("element", { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE }, [{ text: "" }]);
+
+export const defaultDefinitionListBlock = () =>
+  slatejsx("element", { type: DEFINITION_LIST_ELEMENT_TYPE }, [defaultDefinitionTermBlock()]);
diff --git a/src/components/SlateEditor/plugins/definitionList/utils/defaultBlocks.ts b/src/components/SlateEditor/plugins/definitionList/utils/defaultBlocks.ts
deleted file mode 100644
index a2be0a2f59..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/utils/defaultBlocks.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import { jsx as slatejsx } from "slate-hyperscript";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
-
-export const definitionTerm = () => slatejsx("element", { type: TYPE_DEFINITION_TERM }, [{ text: "" }]);
-
-export const definitionDescription = () => slatejsx("element", { type: TYPE_DEFINITION_DESCRIPTION }, [{ text: "" }]);
-
-export const definitionList = () => slatejsx("element", { type: TYPE_DEFINITION_LIST }, [definitionTerm()]);
diff --git a/src/components/SlateEditor/plugins/definitionList/utils/hasDefinitionListItem.ts b/src/components/SlateEditor/plugins/definitionList/utils/hasDefinitionListItem.ts
deleted file mode 100644
index 74e024417a..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/utils/hasDefinitionListItem.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import { Editor, Element } from "slate";
-import { isDefinitionListItem } from "./isDefinitionListItem";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_TERM } from "../types";
-
-const hasDefinitionListItem = (editor: Editor) => {
-  // For all selected list elements
-  for (const [, path] of Editor.nodes(editor, {
-    match: (node) =>
-      Element.isElement(node) && (node.type === TYPE_DEFINITION_DESCRIPTION || node.type === TYPE_DEFINITION_TERM),
-  })) {
-    if (isDefinitionListItem(editor, path)) {
-      return true;
-    }
-
-    return false;
-  }
-  return false;
-};
-
-export default hasDefinitionListItem;
diff --git a/src/components/SlateEditor/plugins/definitionList/utils/isDefinitionListItem.ts b/src/components/SlateEditor/plugins/definitionList/utils/isDefinitionListItem.ts
deleted file mode 100644
index 409d025182..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/utils/isDefinitionListItem.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import { Editor, Range, Path } from "slate";
-
-export const isDefinitionListItem = (editor: Editor, path: Path) =>
-  Range.isRange(editor.selection) && Range.includes(editor.selection, path.concat(0));
diff --git a/src/components/SlateEditor/plugins/definitionList/utils/isOnlySelectionOfDefinitionList.ts b/src/components/SlateEditor/plugins/definitionList/utils/isOnlySelectionOfDefinitionList.ts
deleted file mode 100644
index 5e255922f8..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/utils/isOnlySelectionOfDefinitionList.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import { Editor, Element } from "slate";
-import { isDefinitionListItem } from "./isDefinitionListItem";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
-
-const isOnlySelectionOfDefinitionList = (editor: Editor) => {
-  let hasListItems = false;
-
-  for (const [, path] of Editor.nodes(editor, {
-    match: (node, path) =>
-      Element.isElement(node) &&
-      (node.type === TYPE_DEFINITION_DESCRIPTION || node.type === TYPE_DEFINITION_TERM) &&
-      isDefinitionListItem(editor, path),
-  })) {
-    const [parentNode] = Editor.parent(editor, path);
-    if (Element.isElement(parentNode) && parentNode.type === TYPE_DEFINITION_LIST) {
-      hasListItems = true;
-      continue;
-    }
-    return false;
-  }
-  return hasListItems;
-};
-
-export default isOnlySelectionOfDefinitionList;
diff --git a/src/components/SlateEditor/plugins/definitionList/utils/toggleDefinitionList.ts b/src/components/SlateEditor/plugins/definitionList/utils/toggleDefinitionList.ts
deleted file mode 100644
index dd4b2c6b47..0000000000
--- a/src/components/SlateEditor/plugins/definitionList/utils/toggleDefinitionList.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Copyright (c) 2023-present, NDLA.
- *
- * This source code is licensed under the GPLv3 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-import { Editor, Range, Element, Transforms } from "slate";
-import { isDefinitionListItem } from "./isDefinitionListItem";
-import isOnlySelectionOfDefinitionList from "./isOnlySelectionOfDefinitionList";
-import { firstTextBlockElement } from "../../../utils/normalizationHelpers";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_TERM, TYPE_DEFINITION_LIST } from "../types";
-
-export const toggleDefinitionList = (editor: Editor) => {
-  if (!Range.isRange(editor.selection)) {
-    return;
-  }
-  const isSelected = isOnlySelectionOfDefinitionList(editor);
-
-  if (isSelected) {
-    return Transforms.liftNodes(editor, {
-      match: (node, path) =>
-        Element.isElement(node) &&
-        (node.type === TYPE_DEFINITION_DESCRIPTION || node.type === TYPE_DEFINITION_TERM) &&
-        isDefinitionListItem(editor, path),
-      mode: "all",
-    });
-  } else {
-    Editor.withoutNormalizing(editor, () => {
-      // The withoutNormalizing function is within its own scope and the selection check above does then not follow
-      if (!Range.isRange(editor.selection)) {
-        return;
-      }
-
-      Transforms.setNodes(
-        editor,
-        { type: TYPE_DEFINITION_LIST },
-        {
-          match: (node) => Element.isElement(node) && firstTextBlockElement.includes(node.type),
-          at: Editor.unhangRange(editor, editor.selection),
-          mode: "lowest",
-        },
-      );
-    });
-  }
-};
diff --git a/src/components/SlateEditor/plugins/span/index.tsx b/src/components/SlateEditor/plugins/span/index.tsx
index 712d6c929e..00e0a153c0 100644
--- a/src/components/SlateEditor/plugins/span/index.tsx
+++ b/src/components/SlateEditor/plugins/span/index.tsx
@@ -23,7 +23,10 @@ import { isSpanElement } from "./queries";
 import { SPAN_ELEMENT_TYPE, SPAN_PLUGIN } from "./types";
 import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
 import { BLOCK_QUOTE_ELEMENT_TYPE } from "../blockquote/blockquoteTypes";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_TERM } from "../definitionList/types";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "../definitionList/definitionListTypes";
 import { SUMMARY_ELEMENT_TYPE } from "../details/summaryTypes";
 import { TYPE_TABLE_CELL } from "../table/types";
 
@@ -45,8 +48,8 @@ const normalizerConfig: NormalizerConfig = {
       BLOCK_QUOTE_ELEMENT_TYPE,
       TYPE_TABLE_CELL,
       LIST_ITEM_ELEMENT_TYPE,
-      TYPE_DEFINITION_TERM,
-      TYPE_DEFINITION_DESCRIPTION,
+      DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+      DEFINITION_TERM_ELEMENT_TYPE,
       SUMMARY_ELEMENT_TYPE,
       SPAN_ELEMENT_TYPE,
       NOOP_ELEMENT_TYPE,
diff --git a/src/components/SlateEditor/plugins/toolbar/ToolbarBlockOptions.tsx b/src/components/SlateEditor/plugins/toolbar/ToolbarBlockOptions.tsx
index 54f6079eab..d22a95e4f2 100644
--- a/src/components/SlateEditor/plugins/toolbar/ToolbarBlockOptions.tsx
+++ b/src/components/SlateEditor/plugins/toolbar/ToolbarBlockOptions.tsx
@@ -15,7 +15,7 @@ import { ToolbarCategoryProps } from "./SlateToolbar";
 import { BlockType } from "./toolbarState";
 import { ToolbarToggleButton, ToolbarToggleGroupRoot } from "./ToolbarToggle";
 import toggleBlock from "../../utils/toggleBlock";
-import { toggleDefinitionList } from "../definitionList/utils/toggleDefinitionList";
+import { toggleDefinitionList } from "../definitionList/transforms/toggleDefinitionList";
 
 const getCurrentBlockValues = (editor: Editor) => {
   const [currentListBlock] =
diff --git a/src/components/SlateEditor/plugins/toolbar/handleMenuClicks.ts b/src/components/SlateEditor/plugins/toolbar/handleMenuClicks.ts
index b0abe5f094..f782510886 100644
--- a/src/components/SlateEditor/plugins/toolbar/handleMenuClicks.ts
+++ b/src/components/SlateEditor/plugins/toolbar/handleMenuClicks.ts
@@ -14,7 +14,6 @@ import { BlockType, InlineType, TextType, getEditorAncestors } from "./toolbarSt
 import toggleBlock from "../../utils/toggleBlock";
 import { insertComment } from "../comment/inline/utils";
 import { insertInlineConcept } from "../concept/inline/utils";
-import { toggleDefinitionList } from "../definitionList/utils/toggleDefinitionList";
 import { TYPE_HEADING } from "../heading/types";
 import { insertLink, unwrapLink } from "../link/utils";
 import { insertMathml } from "../mathml/utils";
@@ -22,6 +21,7 @@ import { TYPE_PARAGRAPH } from "../paragraph/types";
 import { SpanElement } from "../span";
 import { SPAN_ELEMENT_TYPE } from "../span/types";
 import { toggleCellAlign } from "../table/slateActions";
+import { toggleDefinitionList } from "../definitionList/transforms/toggleDefinitionList";
 
 type TextElements = ParagraphElement | HeadingElement | SpanElement;
 const defaultValueState: Partial<Record<Element["type"], Partial<TextElements>>> = {
diff --git a/src/components/SlateEditor/utils/normalizationHelpers.ts b/src/components/SlateEditor/utils/normalizationHelpers.ts
index dc3947d596..7194c946bc 100644
--- a/src/components/SlateEditor/utils/normalizationHelpers.ts
+++ b/src/components/SlateEditor/utils/normalizationHelpers.ts
@@ -13,7 +13,6 @@ import { ASIDE_ELEMENT_TYPE } from "../plugins/aside/asideTypes";
 import { AUDIO_ELEMENT_TYPE } from "../plugins/audio/audioTypes";
 import { CODE_BLOCK_ELEMENT_TYPE } from "../plugins/codeBlock/types";
 import { COMMENT_BLOCK_ELEMENT_TYPE } from "../plugins/comment/block/types";
-import { TYPE_DEFINITION_LIST } from "../plugins/definitionList/types";
 import { DETAILS_ELEMENT_TYPE } from "../plugins/details/detailsTypes";
 import { TYPE_EMBED_ERROR } from "../plugins/embed/types";
 import { TYPE_EXTERNAL } from "../plugins/external/types";
@@ -32,6 +31,7 @@ import { TYPE_CONTENT_LINK, TYPE_LINK } from "../plugins/link/types";
 import { FRAMED_CONTENT_ELEMENT_TYPE } from "../plugins/framedContent/framedContentTypes";
 import { BLOCK_QUOTE_ELEMENT_TYPE } from "../plugins/blockquote/blockquoteTypes";
 import { REPHRASE_ELEMENT_TYPE } from "../plugins/rephrase/rephraseTypes";
+import { DEFINITION_LIST_ELEMENT_TYPE } from "../plugins/definitionList/definitionListTypes";
 
 export const firstTextBlockElement: Element["type"][] = [TYPE_PARAGRAPH, TYPE_HEADING, BLOCK_QUOTE_ELEMENT_TYPE];
 
@@ -51,7 +51,7 @@ export const textBlockElements: Element["type"][] = [
   ASIDE_ELEMENT_TYPE,
   FRAMED_CONTENT_ELEMENT_TYPE,
   DETAILS_ELEMENT_TYPE,
-  TYPE_DEFINITION_LIST,
+  DEFINITION_LIST_ELEMENT_TYPE,
   TYPE_GRID,
   REPHRASE_ELEMENT_TYPE,
   SPAN_ELEMENT_TYPE,
diff --git a/src/containers/ArticlePage/FrontpageArticlePage/components/frontpagePlugins.ts b/src/containers/ArticlePage/FrontpageArticlePage/components/frontpagePlugins.ts
index 9b9660ac72..e0870a90fd 100644
--- a/src/containers/ArticlePage/FrontpageArticlePage/components/frontpagePlugins.ts
+++ b/src/containers/ArticlePage/FrontpageArticlePage/components/frontpagePlugins.ts
@@ -19,7 +19,6 @@ import { blockConceptPlugin } from "../../../../components/SlateEditor/plugins/c
 import { inlineConceptPlugin } from "../../../../components/SlateEditor/plugins/concept/inline";
 import { contactBlockPlugin } from "../../../../components/SlateEditor/plugins/contactBlock";
 import { copyrightPlugin } from "../../../../components/SlateEditor/plugins/copyright";
-import { definitionListPlugin } from "../../../../components/SlateEditor/plugins/definitionList";
 import { detailsPlugin } from "../../../../components/SlateEditor/plugins/details/detailsPlugin";
 import { summaryPlugin } from "../../../../components/SlateEditor/plugins/details/summaryPlugin";
 import { divPlugin } from "../../../../components/SlateEditor/plugins/div";
@@ -53,6 +52,9 @@ import { markPlugin } from "../../../../components/SlateEditor/plugins/mark";
 import { listPlugin } from "../../../../components/SlateEditor/plugins/list";
 import { idPlugin } from "../../../../components/SlateEditor/plugins/id/idPlugin";
 import { rephrasePlugin } from "../../../../components/SlateEditor/plugins/rephrase/rephrasePlugin";
+import { definitionListPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionListPlugin";
+import { definitionTermPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionTermPlugin";
+import { definitionDescriptionPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin";
 
 // Plugins are checked from last to first
 export const frontpagePlugins: SlatePlugin[] = [
@@ -97,6 +99,8 @@ export const frontpagePlugins: SlatePlugin[] = [
   saveHotkeyPlugin,
   markPlugin,
   definitionListPlugin,
+  definitionTermPlugin,
+  definitionDescriptionPlugin,
   listPlugin,
   gridPlugin,
   pitchPlugin,
diff --git a/src/containers/ArticlePage/LearningResourcePage/components/learningResourcePlugins.ts b/src/containers/ArticlePage/LearningResourcePage/components/learningResourcePlugins.ts
index b10d88bcc1..ee7f76175e 100644
--- a/src/containers/ArticlePage/LearningResourcePage/components/learningResourcePlugins.ts
+++ b/src/containers/ArticlePage/LearningResourcePage/components/learningResourcePlugins.ts
@@ -18,7 +18,6 @@ import { commentInlinePlugin } from "../../../../components/SlateEditor/plugins/
 import { blockConceptPlugin } from "../../../../components/SlateEditor/plugins/concept/block";
 import { inlineConceptPlugin } from "../../../../components/SlateEditor/plugins/concept/inline";
 import { copyrightPlugin } from "../../../../components/SlateEditor/plugins/copyright";
-import { definitionListPlugin } from "../../../../components/SlateEditor/plugins/definitionList";
 import { detailsPlugin } from "../../../../components/SlateEditor/plugins/details/detailsPlugin";
 import { summaryPlugin } from "../../../../components/SlateEditor/plugins/details/summaryPlugin";
 import { divPlugin } from "../../../../components/SlateEditor/plugins/div";
@@ -49,6 +48,9 @@ import { markPlugin } from "../../../../components/SlateEditor/plugins/mark";
 import { listPlugin } from "../../../../components/SlateEditor/plugins/list";
 import { idPlugin } from "../../../../components/SlateEditor/plugins/id/idPlugin";
 import { rephrasePlugin } from "../../../../components/SlateEditor/plugins/rephrase/rephrasePlugin";
+import { definitionListPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionListPlugin";
+import { definitionTermPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionTermPlugin";
+import { definitionDescriptionPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin";
 
 export const learningResourcePlugins: SlatePlugin[] = [
   idPlugin,
@@ -91,6 +93,8 @@ export const learningResourcePlugins: SlatePlugin[] = [
   saveHotkeyPlugin,
   markPlugin,
   definitionListPlugin,
+  definitionTermPlugin,
+  definitionDescriptionPlugin,
   listPlugin,
   gridPlugin,
   disclaimerPlugin,
diff --git a/src/containers/ArticlePage/TopicArticlePage/components/topicArticlePlugins.ts b/src/containers/ArticlePage/TopicArticlePage/components/topicArticlePlugins.ts
index 103c9a4282..9bf46a372c 100644
--- a/src/containers/ArticlePage/TopicArticlePage/components/topicArticlePlugins.ts
+++ b/src/containers/ArticlePage/TopicArticlePage/components/topicArticlePlugins.ts
@@ -10,7 +10,6 @@ import { SlatePlugin } from "../../../../components/SlateEditor/interfaces";
 import { blockQuotePlugin } from "../../../../components/SlateEditor/plugins/blockquote/blockquotePlugin";
 import { commentInlinePlugin } from "../../../../components/SlateEditor/plugins/comment/inline";
 import { inlineConceptPlugin } from "../../../../components/SlateEditor/plugins/concept/inline";
-import { definitionListPlugin } from "../../../../components/SlateEditor/plugins/definitionList";
 import { divPlugin } from "../../../../components/SlateEditor/plugins/div";
 import { dndPlugin } from "../../../../components/SlateEditor/plugins/DND";
 import { linkPlugin } from "../../../../components/SlateEditor/plugins/link";
@@ -29,6 +28,9 @@ import { breakPlugin } from "../../../../components/SlateEditor/plugins/break";
 import { inlineNavigationPlugin } from "@ndla/editor";
 import { idPlugin } from "../../../../components/SlateEditor/plugins/id/idPlugin";
 import { rephrasePlugin } from "../../../../components/SlateEditor/plugins/rephrase/rephrasePlugin";
+import { definitionListPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionListPlugin";
+import { definitionTermPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionTermPlugin";
+import { definitionDescriptionPlugin } from "../../../../components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin";
 
 // Plugins are checked from last to first
 export const topicArticlePlugins: SlatePlugin[] = [
@@ -46,6 +48,8 @@ export const topicArticlePlugins: SlatePlugin[] = [
   // unwrapping (jumping out of block) will not work.
   blockQuotePlugin,
   definitionListPlugin,
+  definitionDescriptionPlugin,
+  definitionTermPlugin,
   listPlugin,
   inlineConceptPlugin,
   commentInlinePlugin,
diff --git a/src/util/articleContentConverter.tsx b/src/util/articleContentConverter.tsx
index ea72615123..ca365543b7 100644
--- a/src/util/articleContentConverter.tsx
+++ b/src/util/articleContentConverter.tsx
@@ -30,7 +30,9 @@ import { blockConceptSerializer } from "../components/SlateEditor/plugins/concep
 import { inlineConceptSerializer } from "../components/SlateEditor/plugins/concept/inline";
 import { contactBlockSerializer } from "../components/SlateEditor/plugins/contactBlock";
 import { copyrightSerializer } from "../components/SlateEditor/plugins/copyright";
-import { definitionListSerializer } from "../components/SlateEditor/plugins/definitionList";
+import { definitionDescriptionSerializer } from "../components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin";
+import { definitionListSerializer } from "../components/SlateEditor/plugins/definitionList/definitionListPlugin";
+import { definitionTermSerializer } from "../components/SlateEditor/plugins/definitionList/definitionTermPlugin";
 import { detailsSerializer } from "../components/SlateEditor/plugins/details/detailsSerializer";
 import { summarySerializer } from "../components/SlateEditor/plugins/details/summarySerializer";
 import { divSerializer } from "../components/SlateEditor/plugins/div";
@@ -94,6 +96,8 @@ const extendedRules: SlateSerializer<any>[] = [
   headingSerializer,
   listSerializer,
   definitionListSerializer,
+  definitionDescriptionSerializer,
+  definitionTermSerializer,
   footnoteSerializer,
   mathmlSerializer,
   inlineConceptSerializer,
@@ -138,6 +142,8 @@ const commonRules: SlateSerializer<any>[] = [
   headingSerializer,
   listSerializer,
   definitionListSerializer,
+  definitionDescriptionSerializer,
+  definitionTermSerializer,
   footnoteSerializer,
   mathmlSerializer,
   inlineConceptSerializer,


Reviews:
user: login: Jonas-C
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T12:05:59Z
user: login: Jonas-C
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T07:44:16Z
user: login: MaPoKen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T07:45:15Z
user: login: MaPoKen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T07:54:22Z
user: login: gunnarvelle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T12:33:40Z
user: login: gunnarvelle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T12:34:13Z
user: login: gunnarvelle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T12:34:45Z
user: login: MaPoKen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T13:29:22Z
user: login: gunnarvelle
body: lgtm
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-23T13:32:47Z

Review comments:
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: Jonas-C
body: Lurer litt på om dette kanskje burde være tre forskjellige plugins?
created_at: 2025-04-10T12:05:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DEFINITION_TERM_PLUGIN,
+} from "./definitionListTypes";
+import { isDefinitionTerm } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizeDTConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
path: src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
user: login: Jonas-C
body: Er det egentlig noe poeng i å ha spesifisere dette? Er det ikke enklere å la parent styre hva som er gyldig?
created_at: 2025-04-11T07:06:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DEFINITION_TERM_PLUGIN,
+} from "./definitionListTypes";
+import { isDefinitionTerm } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizeDTConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+};
+
+export const definitionTermSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dt") {
+      return slatejsx("element", { type: DEFINITION_TERM_ELEMENT_TYPE }, children);
+    }
+  },
+  serialize(node, children) {
+    if (!Element.isElement(node)) return;
+    if (node.type === DEFINITION_TERM_ELEMENT_TYPE) {
path: src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
user: login: Jonas-C
body: ```tsx
if(!isDefinitionTerm(node)) return;
return createHtmlTag({ tag: "dt", children });
```
created_at: 2025-04-11T07:07:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DEFINITION_TERM_PLUGIN,
+} from "./definitionListTypes";
+import { isDefinitionTerm } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizeDTConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+};
+
+export const definitionTermSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dt") {
+      return slatejsx("element", { type: DEFINITION_TERM_ELEMENT_TYPE }, children);
+    }
+  },
+  serialize(node, children) {
+    if (!Element.isElement(node)) return;
+    if (node.type === DEFINITION_TERM_ELEMENT_TYPE) {
+      return createHtmlTag({ tag: "dt", children });
+    }
+  },
+});
+
+export const definitionTermPlugin = createPlugin({
+  name: DEFINITION_TERM_PLUGIN,
+  type: DEFINITION_TERM_ELEMENT_TYPE,
+  normalize: (editor, node, path, logger) => {
+    if (isDefinitionTerm(node)) {
+      if (defaultBlockNormalizer(editor, node, path, normalizeDTConfig, logger)) {
path: src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
user: login: Jonas-C
body: Kan returnere resultatet direkte
created_at: 2025-04-11T07:08:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DEFINITION_TERM_PLUGIN,
+} from "./definitionListTypes";
+import { isDefinitionTerm } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizeDTConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+};
+
+export const definitionTermSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dt") {
path: src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
user: login: Jonas-C
body: Ryddigere å returnere tidlig dersom tag ikke er dt
created_at: 2025-04-11T07:08:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { Node } from "slate";
+import { isElementOfType } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "../definitionListTypes";
+
+export const isDefinitionTerm = (node: Node | undefined) => isElementOfType(node, DEFINITION_TERM_ELEMENT_TYPE);
+
+export const isDefinitionDescription = (node: Node | undefined) =>
+  isElementOfType(node, DEFINITION_DESCRIPTION_ELEMENT_TYPE);
+
+export const isDefinitionList = (node: Node | undefined) => isElementOfType(node, DEFINITION_LIST_ELEMENT_TYPE);
path: src/components/SlateEditor/plugins/definitionList/queries/definitionListQueries.ts
user: login: Jonas-C
body: Disse er vel på formatet `is${TYPE}Element` alle andre steder
created_at: 2025-04-11T07:11:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Descendant, Element, Path, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onBackspace } from "./handlers/onBackspace";
+import { onEnter } from "./handlers/onEnter";
+import { onTab } from "./handlers/onTab";
+import { isDefinitionList } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizerDLConfig: NormalizerConfig = {
+  nodes: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+export const definitionListSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dl") {
+      return slatejsx("element", { type: DEFINITION_LIST_ELEMENT_TYPE }, children);
+    }
+  },
+  serialize(node, children) {
+    if (!Element.isElement(node)) return;
+    if (node.type === DEFINITION_LIST_ELEMENT_TYPE) {
+      return createHtmlTag({ tag: "dl", children });
+    }
+  },
+});
+
+export const definitionListPlugin = createPlugin<DefinitionListType>({
+  name: DEFINITION_LIST_PLUGIN,
+  type: DEFINITION_LIST_ELEMENT_TYPE,
+  shortcuts: {
+    dentList: { keyCondition: isKeyHotkey("shift?+tab"), handler: onTab },
+    listItemInsertion: { keyCondition: isKeyHotkey("enter"), handler: onEnter },
+    listItemDeletion: { keyCondition: isKeyHotkey("backspace"), handler: onBackspace },
+  },
+  normalize: (editor, node, path, logger) => {
+    if (isDefinitionList(node)) {
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: Jonas-C
body: Kanskje greit å returne tidlig her? Så slipper vi et nivå med indentering
created_at: 2025-04-11T07:12:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Descendant, Element, Path, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onBackspace } from "./handlers/onBackspace";
+import { onEnter } from "./handlers/onEnter";
+import { onTab } from "./handlers/onTab";
+import { isDefinitionList } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizerDLConfig: NormalizerConfig = {
+  nodes: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+export const definitionListSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dl") {
+      return slatejsx("element", { type: DEFINITION_LIST_ELEMENT_TYPE }, children);
+    }
+  },
+  serialize(node, children) {
+    if (!Element.isElement(node)) return;
+    if (node.type === DEFINITION_LIST_ELEMENT_TYPE) {
+      return createHtmlTag({ tag: "dl", children });
+    }
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: Jonas-C
body: ```tsx
if(!isDefinitionListElement(node)) return;
return createHtmlTag({ tag: "dl", children });
```
created_at: 2025-04-11T07:16:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Descendant, Element, Path, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onBackspace } from "./handlers/onBackspace";
+import { onEnter } from "./handlers/onEnter";
+import { onTab } from "./handlers/onTab";
+import { isDefinitionList } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizerDLConfig: NormalizerConfig = {
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: Jonas-C
body: Burde ikke denne definere `firstNode` egentlig?
created_at: 2025-04-11T07:16:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_DESCRIPTION_PLUGIN,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionDescriptionType,
+} from "./definitionListTypes";
+import { isDefinitionDescription } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizerDDConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+  previous: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+
+export const definitionDescriptionSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dd") {
+      return slatejsx("element", { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE }, children);
+    }
+  },
+  serialize(node, children) {
+    if (!Element.isElement(node)) return;
+    if (node.type === DEFINITION_DESCRIPTION_ELEMENT_TYPE) {
+      return createHtmlTag({ tag: "dd", children });
+    }
path: src/components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin.ts
user: login: Jonas-C
body: ```tsx
if(!isDefinitionDescriptionElement(node)) return;
return createHtmlTag({ tag: "dd", children });
```
created_at: 2025-04-11T07:17:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_DESCRIPTION_PLUGIN,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionDescriptionType,
+} from "./definitionListTypes";
+import { isDefinitionDescription } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizerDDConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+  previous: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+
+export const definitionDescriptionSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dd") {
+      return slatejsx("element", { type: DEFINITION_DESCRIPTION_ELEMENT_TYPE }, children);
+    }
+  },
+  serialize(node, children) {
+    if (!Element.isElement(node)) return;
+    if (node.type === DEFINITION_DESCRIPTION_ELEMENT_TYPE) {
+      return createHtmlTag({ tag: "dd", children });
+    }
+  },
+});
+
+export const definitionDescriptionPlugin = createPlugin<DefinitionDescriptionType>({
+  name: DEFINITION_DESCRIPTION_PLUGIN,
+  type: DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  normalize: (editor, node, path, logger) => {
+    if (isDefinitionDescription(node)) {
+      if (defaultBlockNormalizer(editor, node, path, normalizerDDConfig, logger)) {
+        return true;
+      }
+    }
+    return false;
path: src/components/SlateEditor/plugins/definitionList/definitionDescriptionPlugin.ts
user: login: Jonas-C
body: ```tsx
if(!isDefinitionDescription(node)) return;
return defaultBlockNormalizer(editor, node, path, normalizerDDConfig, logger);
```
created_at: 2025-04-11T07:19:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,42 +7,34 @@
  */
 
 import { KeyboardEvent } from "react";
-import { Editor, Element, Transforms, Range, Point } from "slate";
+import { Editor, Transforms, Range, Point } from "slate";
 import hasNodeOfType from "../../../utils/hasNodeOfType";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
+import { Logger } from "@ndla/editor";
+import { DEFINITION_LIST_ELEMENT_TYPE } from "../definitionListTypes";
+import { isDefinitionDescription, isDefinitionTerm } from "../queries/definitionListQueries";
 
-const onBackspace = (
-  e: KeyboardEvent<HTMLDivElement>,
-  editor: Editor,
-  nextOnKeyDown: ((e: KeyboardEvent<HTMLDivElement>) => void) | undefined,
-) => {
-  if (!editor.selection) return nextOnKeyDown?.(e);
-  const isDefinition = hasNodeOfType(editor, TYPE_DEFINITION_LIST);
+export const onBackspace = (editor: Editor, e: KeyboardEvent<HTMLDivElement>, logger: Logger) => {
+  if (!editor.selection) return false;
+  const isDefinition = hasNodeOfType(editor, DEFINITION_LIST_ELEMENT_TYPE);
 
   if (!isDefinition) {
-    return nextOnKeyDown?.(e);
+    return false;
   }
path: src/components/SlateEditor/plugins/definitionList/handlers/onBackspace.ts
user: login: Jonas-C
body: ```tsx
if(!editor.selection || !Range.isCollapsed(editor.selection) || !hasNodeOfType(editor, DEFINITION_LIST_ELEMENT_TYPE)) return false;
```
created_at: 2025-04-11T07:22:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,42 +7,34 @@
  */
 
 import { KeyboardEvent } from "react";
-import { Editor, Element, Transforms, Range, Point } from "slate";
+import { Editor, Transforms, Range, Point } from "slate";
 import hasNodeOfType from "../../../utils/hasNodeOfType";
-import { TYPE_DEFINITION_DESCRIPTION, TYPE_DEFINITION_LIST, TYPE_DEFINITION_TERM } from "../types";
+import { Logger } from "@ndla/editor";
+import { DEFINITION_LIST_ELEMENT_TYPE } from "../definitionListTypes";
+import { isDefinitionDescription, isDefinitionTerm } from "../queries/definitionListQueries";
 
-const onBackspace = (
-  e: KeyboardEvent<HTMLDivElement>,
-  editor: Editor,
-  nextOnKeyDown: ((e: KeyboardEvent<HTMLDivElement>) => void) | undefined,
-) => {
-  if (!editor.selection) return nextOnKeyDown?.(e);
-  const isDefinition = hasNodeOfType(editor, TYPE_DEFINITION_LIST);
+export const onBackspace = (editor: Editor, e: KeyboardEvent<HTMLDivElement>, logger: Logger) => {
+  if (!editor.selection) return false;
+  const isDefinition = hasNodeOfType(editor, DEFINITION_LIST_ELEMENT_TYPE);
 
   if (!isDefinition) {
-    return nextOnKeyDown?.(e);
+    return false;
   }
 
-  const [selectedDefinitionItem, selectedDefinitionItemPath] = Editor.parent(editor, editor.selection.anchor.path);
-  if (selectedDefinitionItem) {
-    if (
-      Element.isElement(selectedDefinitionItem) &&
-      (selectedDefinitionItem.type === TYPE_DEFINITION_DESCRIPTION ||
-        selectedDefinitionItem.type === TYPE_DEFINITION_TERM)
-    ) {
-      if (Range.isCollapsed(editor.selection)) {
-        const [, firstItemNodePath] = Editor.node(editor, [...selectedDefinitionItemPath, 0]);
-        if (Point.equals(Range.start(editor.selection), Editor.start(editor, firstItemNodePath))) {
-          e.preventDefault();
-          return Transforms.liftNodes(editor, {
-            at: selectedDefinitionItemPath,
-          });
-        }
+  const [selectedDefinitionNode, selectedDefinitionPath] = Editor.parent(editor, editor.selection.anchor.path);
+  if (isDefinitionTerm(selectedDefinitionNode) || isDefinitionDescription(selectedDefinitionNode)) {
+    if (Range.isCollapsed(editor.selection)) {
+      const [, firstItemNodePath] = Editor.node(editor, [...selectedDefinitionPath, 0]);
+      if (Point.equals(Range.start(editor.selection), Editor.start(editor, firstItemNodePath))) {
+        e.preventDefault();
+        Transforms.liftNodes(editor, {
+          at: selectedDefinitionPath,
+        });
+        logger.log("Backspace at the start of node, lift node out of list.");
path: src/components/SlateEditor/plugins/definitionList/handlers/onBackspace.ts
user: login: Jonas-C
body: Sikkert greit å logge før vi gjør transformeringen
created_at: 2025-04-11T07:25:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2023-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import type { KeyboardEvent } from "react";
+import { Editor, Transforms, Node, NodeEntry, Element } from "slate";
+import { Logger } from "@ndla/editor";
+import getCurrentBlock from "../../../utils/getCurrentBlock";
+import hasNodeOfType from "../../../utils/hasNodeOfType";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+} from "../definitionListTypes";
+import { isDefinitionTerm, isDefinitionDescription, isDefinitionList } from "../queries/definitionListQueries";
+
+export const onTab = (editor: Editor, event: KeyboardEvent<HTMLDivElement>, logger: Logger) => {
+  event.preventDefault();
+  const isDefinition = hasNodeOfType(editor, DEFINITION_LIST_ELEMENT_TYPE);
+  if (!isDefinition || !editor.selection) {
+    return false;
+  }
path: src/components/SlateEditor/plugins/definitionList/handlers/onTab.ts
user: login: Jonas-C
body: Sikkert fint å sjekke at editor.selection eksisterer før man sjekker etter noden. Kan spare oss for en unødvendig query.
created_at: 2025-04-11T07:37:33Z
author_association: CONTRIBUTOR
path: src/components/SlateEditor/plugins/definitionList/handlers/onTab.ts
user: login: Jonas-C
body: Denne virker overdrevent komplisert
created_at: 2025-04-11T07:38:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,40 +7,30 @@
  */
 
 import type { KeyboardEvent } from "react";
-import { Editor, Element, Path, Transforms, Node, Range, Point } from "slate";
-import { ReactEditor } from "slate-react";
-import { getEditorAncestors } from "../../toolbar/toolbarState";
-import { TYPE_DEFINITION_TERM, TYPE_DEFINITION_DESCRIPTION } from "../types";
-import { definitionDescription, definitionTerm } from "../utils/defaultBlocks";
+import { Editor, Element, Path, Transforms, Node, Range, Point, NodeEntry } from "slate";
+import { Logger } from "@ndla/editor";
+import { isDefinitionTerm, isDefinitionDescription } from "../queries/definitionListQueries";
+import { defaultDefinitionTermBlock, defaultDefinitionDescriptionBlock } from "../utils";
+import { DEFINITION_DESCRIPTION_ELEMENT_TYPE, DEFINITION_TERM_ELEMENT_TYPE } from "../definitionListTypes";
 
-const onEnter = (
-  e: KeyboardEvent<HTMLDivElement>,
-  editor: Editor,
-  nextOnKeyDown: ((e: KeyboardEvent<HTMLDivElement>) => void) | undefined,
-) => {
-  if ((e.shiftKey && nextOnKeyDown) || (!editor.selection && nextOnKeyDown)) {
-    return nextOnKeyDown(e);
-  } else if (!editor.selection) return;
-
-  const [firstChild, secondChild] = getEditorAncestors(editor, true);
-  const selectedDefinitionItem =
-    firstChild.type === TYPE_DEFINITION_DESCRIPTION || firstChild.type === TYPE_DEFINITION_TERM
-      ? firstChild
-      : secondChild;
-
-  if (!selectedDefinitionItem) {
-    return nextOnKeyDown?.(e);
+export const onEnter = (editor: Editor, e: KeyboardEvent<HTMLDivElement>, logger: Logger) => {
path: src/components/SlateEditor/plugins/definitionList/handlers/onEnter.ts
user: login: Jonas-C
body: Tror du denne kunne blitt forenklet hvis vi extendet `insertFragment` istedenfor?
created_at: 2025-04-11T07:42:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DEFINITION_TERM_PLUGIN,
+} from "./definitionListTypes";
+import { isDefinitionTerm } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizeDTConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
path: src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
user: login: MaPoKen
body: Vurderte det samme, så vi fjernern!!
created_at: 2025-04-11T07:45:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Descendant, Element, Path, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onBackspace } from "./handlers/onBackspace";
+import { onEnter } from "./handlers/onEnter";
+import { onTab } from "./handlers/onTab";
+import { isDefinitionList } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizerDLConfig: NormalizerConfig = {
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: MaPoKen
body: og last node 🤠 
created_at: 2025-04-11T07:54:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { jsx as slatejsx } from "slate-hyperscript";
+import { Descendant, Element } from "slate";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DEFINITION_TERM_PLUGIN,
+} from "./definitionListTypes";
+import { isDefinitionTerm } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizeDTConfig: NormalizerConfig = {
+  parent: {
+    allowed: [DEFINITION_LIST_ELEMENT_TYPE],
+  },
+};
+
+export const definitionTermSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag === "dt") {
+      return slatejsx("element", { type: DEFINITION_TERM_ELEMENT_TYPE }, children);
+    }
+  },
+  serialize(node, children) {
+    if (!Element.isElement(node)) return;
+    if (node.type === DEFINITION_TERM_ELEMENT_TYPE) {
path: src/components/SlateEditor/plugins/definitionList/definitionTermPlugin.ts
user: login: gunnarvelle
body: Var du ikkje enig i dette @MaPoKen ?
created_at: 2025-04-14T12:33:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Descendant, Path, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onBackspace } from "./handlers/onBackspace";
+import { onTab } from "./handlers/onTab";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+import { isDefinitionListElement } from "./queries/definitionListQueries";
+import { onEnter } from "./handlers/onEnter";
+
+const normalizerDLConfig: NormalizerConfig = {
+  firstNode: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+  nodes: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+export const definitionListSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag !== "dl") return;
+    return slatejsx("element", { type: DEFINITION_LIST_ELEMENT_TYPE }, children);
+  },
+  serialize(node, children) {
+    if (!isDefinitionListElement(node)) return;
+    return createHtmlTag({ tag: "dl", children });
+  },
+});
+
+export const definitionListPlugin = createPlugin<DefinitionListType>({
+  name: DEFINITION_LIST_PLUGIN,
+  type: DEFINITION_LIST_ELEMENT_TYPE,
+  shortcuts: {
+    // TODO: Add transforms logic to replace some onKeyDown functionality
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: gunnarvelle
body: Skal denne todoen gjøres noe med?
created_at: 2025-04-14T12:34:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Descendant, Element, Path, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onBackspace } from "./handlers/onBackspace";
+import { onEnter } from "./handlers/onEnter";
+import { onTab } from "./handlers/onTab";
+import { isDefinitionList } from "./queries/definitionListQueries";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+
+const normalizerDLConfig: NormalizerConfig = {
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: gunnarvelle
body: Skal det være en lastNode her? Eg aner ikkje men siden du kommenterte det.
created_at: 2025-04-14T12:34:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2025-present, NDLA.
+ *
+ * This source code is licensed under the GPLv3 license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ */
+
+import { isKeyHotkey } from "is-hotkey";
+import { Editor, Descendant, Path, Transforms } from "slate";
+import { jsx as slatejsx } from "slate-hyperscript";
+import { createHtmlTag, createPlugin, createSerializer } from "@ndla/editor";
+import {
+  DEFINITION_DESCRIPTION_ELEMENT_TYPE,
+  DEFINITION_LIST_ELEMENT_TYPE,
+  DEFINITION_LIST_PLUGIN,
+  DEFINITION_TERM_ELEMENT_TYPE,
+  DefinitionListType,
+} from "./definitionListTypes";
+import { onBackspace } from "./handlers/onBackspace";
+import { onTab } from "./handlers/onTab";
+import { defaultBlockNormalizer, NormalizerConfig } from "../../utils/defaultNormalizer";
+import { isDefinitionListElement } from "./queries/definitionListQueries";
+import { onEnter } from "./handlers/onEnter";
+
+const normalizerDLConfig: NormalizerConfig = {
+  firstNode: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+  nodes: {
+    allowed: [DEFINITION_TERM_ELEMENT_TYPE, DEFINITION_DESCRIPTION_ELEMENT_TYPE],
+    defaultType: DEFINITION_TERM_ELEMENT_TYPE,
+  },
+};
+export const definitionListSerializer = createSerializer({
+  deserialize(el: HTMLElement, children: (Descendant | null)[]) {
+    const tag = el.tagName.toLowerCase();
+    if (tag !== "dl") return;
+    return slatejsx("element", { type: DEFINITION_LIST_ELEMENT_TYPE }, children);
+  },
+  serialize(node, children) {
+    if (!isDefinitionListElement(node)) return;
+    return createHtmlTag({ tag: "dl", children });
+  },
+});
+
+export const definitionListPlugin = createPlugin<DefinitionListType>({
+  name: DEFINITION_LIST_PLUGIN,
+  type: DEFINITION_LIST_ELEMENT_TYPE,
+  shortcuts: {
+    // TODO: Add transforms logic to replace some onKeyDown functionality
path: src/components/SlateEditor/plugins/definitionList/definitionListPlugin.ts
user: login: MaPoKen
body: Vil gjøres noe senere, er relevant til logikken som må gjøres i lister. Jonas som foreslo det over 👯 
created_at: 2025-04-14T13:29:22Z
author_association: CONTRIBUTOR

PR comments:
user: login: Jonas-C
created_at: 2025-04-22T10:43:29Z
body: Tror denne begynner å bli klar nå. Gjerne sjekk om dere finner feil (eller noe rart) med oppførselen. Man kan delvis brekke dette ved å kombinere det med lister, men lurer på om det er en oppgave vi skal spare til senere. 
author_association: CONTRIBUTOR
user: login: gunnarvelle
created_at: 2025-04-23T11:18:56Z
body: Om du står på dt og trykker tab blir elementet en dd, men om du står på en dd og trykker tab hopper du ut av editoren.
author_association: MEMBER