Skip to content

Commit

Permalink
port over listSerializer
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas-C committed Jan 17, 2025
1 parent 8207b90 commit 135d4d2
Showing 1 changed file with 123 additions and 0 deletions.
123 changes: 123 additions & 0 deletions packages/editor/src/plugins/list/listSerializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* 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 { Element, Text, type Descendant } from "slate";
import { jsx as slatejsx } from "slate-hyperscript";
import type { SlateSerializer } from "../../types";
import { isElementOfType } from "../../utils/isElementType";
import { LIST_ELEMENT_TYPE, LIST_ITEM_ELEMENT_TYPE } from "./listTypes";
import { createHtmlTag } from "../../utils/serializationHelpers";
import { SOFT_BREAK_ELEMENT_TYPE } from "../softBreak/softBreakTypes";
import { PARAGRAPH_ELEMENT_TYPE } from "../paragraph/paragraphTypes";
import { LINK_ELEMENT_TYPE } from "../link/linkTypes";

const LIST_TAG_TYPES = ["ol", "ul"];

// TODO: I want this to be configurable
const inlines: Element["type"][] = [
// TYPE_CONCEPT_INLINE,
// TYPE_FOOTNOTE,
LINK_ELEMENT_TYPE,
// TYPE_CONTENT_LINK,
// TYPE_MATHML,
// TYPE_COMMENT_INLINE
] as const;

export const listSerializer: SlateSerializer = {
deserialize(el: HTMLElement, children: (Descendant | null)[]) {
const tag = el.tagName.toLowerCase();

// TODO: I don't really like this deserialization. It relies on too many other plugins indirectly. Maybe we could make it configurable?
// Transform children into a new array with all subsequent text/inlines wrapped into a paragraph with serializeAsText
// Assures text/inlines in <li> will be parsed back to html without <p>-tag
children = children.reduce((acc, cur) => {
const lastElement = acc[acc.length - 1];
if (!cur) {
return acc;
} else if (Element.isElement(cur) && !inlines.includes(cur.type)) {
if (cur.type === SOFT_BREAK_ELEMENT_TYPE) {
if (isElementOfType(lastElement, PARAGRAPH_ELEMENT_TYPE) && lastElement.serializeAsText) {
lastElement.children.push({ text: "\n" });
} else {
acc.push(slatejsx("element", { type: PARAGRAPH_ELEMENT_TYPE, serializeAsText: true }, { text: "\n" }));
}
} else {
acc.push(cur);
}
return acc;
} else if (Text.isText(cur) || isElementOfType(cur, inlines)) {
if (isElementOfType(lastElement, PARAGRAPH_ELEMENT_TYPE) && lastElement.serializeAsText) {
lastElement.children.push(cur);
return acc;
} else {
acc.push(slatejsx("element", { type: PARAGRAPH_ELEMENT_TYPE, serializeAsText: true }, cur));
return acc;
}
}
acc.push(cur);
return acc;
}, [] as Descendant[]);

if (tag === "ul") {
return slatejsx("element", { type: LIST_ELEMENT_TYPE, listType: "bulleted-list", data: {} }, children);
}
if (tag === "ol") {
const start = parseInt(el.getAttribute("start") ?? "");
if (el.getAttribute("data-type") === "letters") {
return slatejsx(
"element",
{
type: LIST_ELEMENT_TYPE,
listType: "letter-list",
data: { start: start ? start : undefined },
},
children,
);
}
// Default to numbered list if no type is set.
else {
return slatejsx(
"element",
{
type: LIST_ELEMENT_TYPE,
listType: "numbered-list",
data: { start: start ? start : undefined },
},
children,
);
}
}
if (tag === "li") {
return slatejsx("element", { type: LIST_ITEM_ELEMENT_TYPE }, children);
}
},
serialize(node, children) {
if (!Element.isElement(node)) return;

if (node.type === LIST_ELEMENT_TYPE) {
if (node.listType === "bulleted-list") {
return createHtmlTag({ tag: "ul", children });
}
if (node.listType === "numbered-list") {
const { start } = node.data;
return createHtmlTag({ tag: "ol", data: { start }, children });
}
if (node.listType === "letter-list") {
const { start } = node.data;
return createHtmlTag({ tag: "ol", data: { start, "data-type": "letters" }, children });
}
}
if (node.type === LIST_ITEM_ELEMENT_TYPE) {
// If first child of list-item is a list, it means that an empty paragraph has been removed by
// paragraph serializer. This should not be removed, therefore inserting it when serializing.
const firstChild = node.children[0];
const illegalFirstElement = !Element.isElement(firstChild) || LIST_TAG_TYPES.includes(firstChild.type);
return createHtmlTag({ tag: "li", children: illegalFirstElement ? `<p></p>${children}` : children });
}
},
};

0 comments on commit 135d4d2

Please sign in to comment.