mirror of
https://github.com/JOYCEQL/magic-resume.git
synced 2026-06-01 23:38:48 +02:00
fix: remove trailing empty paragraphs after lists in rich text content
This commit is contained in:
@@ -35,7 +35,11 @@ import {
|
||||
} from "lucide-react";
|
||||
import Highlight from "@tiptap/extension-highlight";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { normalizeLinkHref, stripLegacyRichTextClasses } from "@/lib/richText";
|
||||
import {
|
||||
normalizeLinkHref,
|
||||
stripLegacyRichTextClasses,
|
||||
stripTrailingListParagraph,
|
||||
} from "@/lib/richText";
|
||||
import { BetterSpace } from "./BetterSpace";
|
||||
import { toast } from "sonner";
|
||||
import "@/styles/tiptap.scss";
|
||||
@@ -405,7 +409,7 @@ const RichTextEditor = ({
|
||||
}: RichTextEditorProps) => {
|
||||
const t = useTranslations("richEditor");
|
||||
const initialContent = useMemo(
|
||||
() => stripLegacyRichTextClasses(content),
|
||||
() => stripTrailingListParagraph(stripLegacyRichTextClasses(content)),
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -472,7 +476,9 @@ const RichTextEditor = ({
|
||||
extensions,
|
||||
content: initialContent,
|
||||
onUpdate: ({ editor }) => {
|
||||
onChange(stripLegacyRichTextClasses(editor.getHTML()));
|
||||
onChange(
|
||||
stripTrailingListParagraph(stripLegacyRichTextClasses(editor.getHTML()))
|
||||
);
|
||||
},
|
||||
editorProps,
|
||||
immediatelyRender: false,
|
||||
@@ -480,7 +486,9 @@ const RichTextEditor = ({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const normalizedContent = stripLegacyRichTextClasses(content);
|
||||
const normalizedContent = stripTrailingListParagraph(
|
||||
stripLegacyRichTextClasses(content)
|
||||
);
|
||||
|
||||
if (editor && normalizedContent !== editor.getHTML()) {
|
||||
editor.commands.setContent(normalizedContent, { emitUpdate: false });
|
||||
|
||||
+18
-2
@@ -3,6 +3,8 @@ const EMPTY_PARAGRAPH_REGEX = /<p>(?:\s| |<br\s*\/?>)*<\/p>/gi;
|
||||
const HTML_BREAK_REGEX = /<br\s*\/?>/gi;
|
||||
const HTML_ANY_TAG_REGEX = /<\/?[^>]+>/g;
|
||||
const INVISIBLE_WHITESPACE_REGEX = /[\s\u200B-\u200D\uFEFF]/g;
|
||||
const TRAILING_LIST_PARAGRAPH_REGEX =
|
||||
/(<\/(?:ul|ol)>)\s*<p>(?:\s| |<br\s*\/?>)*<\/p>\s*$/i;
|
||||
const RICH_TEXT_ANCHOR_REGEX = /<a\b([^>]*)>/gi;
|
||||
const CLASS_ATTRIBUTE_REGEX = /\bclass\s*=\s*("([^"]*)"|'([^']*)')/i;
|
||||
const CLASS_ATTRIBUTE_GLOBAL_REGEX = /\bclass\s*=\s*("([^"]*)"|'([^']*)')/gi;
|
||||
@@ -47,13 +49,25 @@ export const stripLegacyRichTextClasses = (content?: string) => {
|
||||
const classes = currentValue
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.filter((className) => !LEGACY_RICH_TEXT_CLASSES.has(className));
|
||||
.filter((className: string) => !LEGACY_RICH_TEXT_CLASSES.has(className));
|
||||
|
||||
return classes.length ? `class="${classes.join(" ")}"` : "";
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const stripTrailingListParagraph = (content?: string) => {
|
||||
if (!content) return "";
|
||||
|
||||
let normalized = content;
|
||||
|
||||
while (TRAILING_LIST_PARAGRAPH_REGEX.test(normalized)) {
|
||||
normalized = normalized.replace(TRAILING_LIST_PARAGRAPH_REGEX, "$1");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
};
|
||||
|
||||
export const normalizeLinkHref = (href?: string) => {
|
||||
if (!href) return null;
|
||||
|
||||
@@ -97,7 +111,9 @@ export const normalizeRichTextContent = (content?: string) => {
|
||||
normalized = escapeHtml(content).replace(/\r\n|\r|\n/g, "<br />");
|
||||
}
|
||||
|
||||
return decorateRichTextAnchors(stripLegacyRichTextClasses(normalized)).replace(
|
||||
return decorateRichTextAnchors(
|
||||
stripTrailingListParagraph(stripLegacyRichTextClasses(normalized))
|
||||
).replace(
|
||||
EMPTY_PARAGRAPH_REGEX,
|
||||
"<p><br /></p>"
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user