style: education preview

This commit is contained in:
JOYCEQL
2026-03-12 00:52:18 +08:00
parent b5468f6c1a
commit 8d4aa024e3
8 changed files with 40 additions and 20 deletions
@@ -3,7 +3,7 @@ import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import SectionWrapper from "../../shared/SectionWrapper";
import { useLocale } from "@/i18n/compat/client";
import { normalizeRichTextContent } from "@/lib/richText";
import { hasMeaningfulRichTextContent, normalizeRichTextContent } from "@/lib/richText";
import { formatDateString } from "@/lib/utils";
interface EducationSectionProps {
@@ -25,7 +25,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{visibleEducation?.map((edu) => (
<motion.div key={edu.id} layout="position" style={{ marginTop: `${globalSettings?.paragraphSpacing}px` }}>
<motion.div layout="position" className="flex items-center gap-2">
<div className={`font-bold ${flexLayout ? "" : "flex-1"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
<div className={`font-bold ${flexLayout ? "" : "flex-[1.5]"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
{edu.school}
</div>
{centerSubtitle && (
@@ -45,7 +45,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{edu.gpa && ` · GPA ${edu.gpa}`}
</motion.div>
)}
{edu.description && (
{hasMeaningfulRichTextContent(edu.description) && (
<motion.div layout="position" className="mt-1 text-baseFont"
style={{ fontSize: `${globalSettings?.baseFontSize || 14}px`, lineHeight: globalSettings?.lineHeight || 1.6 }}
dangerouslySetInnerHTML={{ __html: normalizeRichTextContent(edu.description) }}
@@ -3,7 +3,7 @@ import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import SectionWrapper from "../../shared/SectionWrapper";
import { useLocale } from "@/i18n/compat/client";
import { normalizeRichTextContent } from "@/lib/richText";
import { hasMeaningfulRichTextContent, normalizeRichTextContent } from "@/lib/richText";
import { formatDateString } from "@/lib/utils";
interface EducationSectionProps {
@@ -25,7 +25,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{visibleEducation?.map((edu) => (
<motion.div key={edu.id} layout="position" style={{ marginTop: `${globalSettings?.paragraphSpacing}px` }}>
<motion.div layout="position" className="flex items-center gap-2">
<div className={`font-bold ${flexLayout ? "" : "flex-1"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
<div className={`font-bold ${flexLayout ? "" : "flex-[1.5]"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
{edu.school}
</div>
{centerSubtitle && (
@@ -45,7 +45,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{edu.gpa && ` · GPA ${edu.gpa}`}
</motion.div>
)}
{edu.description && (
{hasMeaningfulRichTextContent(edu.description) && (
<motion.div layout="position" className="mt-1 text-baseFont"
style={{ fontSize: `${globalSettings?.baseFontSize || 14}px`, lineHeight: globalSettings?.lineHeight || 1.6 }}
dangerouslySetInnerHTML={{ __html: normalizeRichTextContent(edu.description) }}
@@ -3,7 +3,7 @@ import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import SectionWrapper from "../../shared/SectionWrapper";
import { useLocale } from "@/i18n/compat/client";
import { normalizeRichTextContent } from "@/lib/richText";
import { hasMeaningfulRichTextContent, normalizeRichTextContent } from "@/lib/richText";
import { formatDateString } from "@/lib/utils";
interface EducationSectionProps {
@@ -25,7 +25,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{visibleEducation?.map((edu) => (
<motion.div key={edu.id} layout="position" style={{ marginTop: `${globalSettings?.paragraphSpacing}px` }}>
<motion.div layout="position" className="flex items-center gap-2">
<div className={`font-bold ${flexLayout ? "" : "flex-1"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
<div className={`font-bold ${flexLayout ? "" : "flex-[1.5]"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
{edu.school}
</div>
{centerSubtitle && (
@@ -45,7 +45,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{edu.gpa && ` · GPA ${edu.gpa}`}
</motion.div>
)}
{edu.description && (
{hasMeaningfulRichTextContent(edu.description) && (
<motion.div layout="position" className="mt-1 text-baseFont"
style={{ fontSize: `${globalSettings?.baseFontSize || 14}px`, lineHeight: globalSettings?.lineHeight || 1.6 }}
dangerouslySetInnerHTML={{ __html: normalizeRichTextContent(edu.description) }}
@@ -3,7 +3,7 @@ import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import SectionWrapper from "../../shared/SectionWrapper";
import { useLocale } from "@/i18n/compat/client";
import { normalizeRichTextContent } from "@/lib/richText";
import { hasMeaningfulRichTextContent, normalizeRichTextContent } from "@/lib/richText";
import { formatDateString } from "@/lib/utils";
interface EducationSectionProps {
@@ -25,7 +25,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{visibleEducation?.map((edu) => (
<motion.div key={edu.id} layout="position" style={{ marginTop: `${globalSettings?.paragraphSpacing}px` }}>
<motion.div layout="position" className="flex items-center gap-2">
<div className={`font-bold ${flexLayout ? "" : "flex-1"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
<div className={`font-bold ${flexLayout ? "" : "flex-[1.5]"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
{edu.school}
</div>
{centerSubtitle && (
@@ -45,7 +45,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{edu.gpa && ` · GPA ${edu.gpa}`}
</motion.div>
)}
{edu.description && (
{hasMeaningfulRichTextContent(edu.description) && (
<motion.div layout="position" className="mt-1 text-baseFont"
style={{ fontSize: `${globalSettings?.baseFontSize || 14}px`, lineHeight: globalSettings?.lineHeight || 1.6 }}
dangerouslySetInnerHTML={{ __html: normalizeRichTextContent(edu.description) }}
@@ -3,7 +3,7 @@ import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import SectionWrapper from "../../shared/SectionWrapper";
import { useLocale } from "@/i18n/compat/client";
import { normalizeRichTextContent } from "@/lib/richText";
import { hasMeaningfulRichTextContent, normalizeRichTextContent } from "@/lib/richText";
import { formatDateString } from "@/lib/utils";
interface EducationSectionProps {
@@ -25,7 +25,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{visibleEducation?.map((edu) => (
<motion.div key={edu.id} layout="position" style={{ marginTop: `${globalSettings?.paragraphSpacing}px` }}>
<motion.div layout="position" className="flex items-center gap-2">
<div className={`font-bold ${flexLayout ? "" : "flex-1"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
<div className={`font-bold ${flexLayout ? "" : "flex-[1.5]"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
{edu.school}
</div>
{centerSubtitle && (
@@ -45,7 +45,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{edu.gpa && ` · GPA ${edu.gpa}`}
</motion.div>
)}
{edu.description && (
{hasMeaningfulRichTextContent(edu.description) && (
<motion.div layout="position" className="mt-1 text-baseFont"
style={{ fontSize: `${globalSettings?.baseFontSize || 14}px`, lineHeight: globalSettings?.lineHeight || 1.6 }}
dangerouslySetInnerHTML={{ __html: normalizeRichTextContent(edu.description) }}
@@ -3,7 +3,7 @@ import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import SectionWrapper from "../../shared/SectionWrapper";
import { useLocale } from "@/i18n/compat/client";
import { normalizeRichTextContent } from "@/lib/richText";
import { hasMeaningfulRichTextContent, normalizeRichTextContent } from "@/lib/richText";
import { formatDateString, cn } from "@/lib/utils";
interface EducationSectionProps {
@@ -57,7 +57,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true, variant
{edu.gpa && ` · GPA ${edu.gpa}`}
</div>
)}
{edu.description && (
{hasMeaningfulRichTextContent(edu.description) && (
<motion.div layout="position" className={cn("mt-1 text-baseFont", isSidebar && " opacity-80")}
style={{
fontSize: `${isSidebar ? (globalSettings?.baseFontSize || 14) - 2 : (globalSettings?.baseFontSize || 14)}px`,
@@ -3,7 +3,7 @@ import { Education, GlobalSettings } from "@/types/resume";
import SectionTitle from "./SectionTitle";
import SectionWrapper from "../../shared/SectionWrapper";
import { useLocale } from "@/i18n/compat/client";
import { normalizeRichTextContent } from "@/lib/richText";
import { hasMeaningfulRichTextContent, normalizeRichTextContent } from "@/lib/richText";
import { formatDateString } from "@/lib/utils";
interface EducationSectionProps {
@@ -25,7 +25,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{visibleEducation?.map((edu) => (
<motion.div key={edu.id} layout="position" style={{ marginTop: `${globalSettings?.paragraphSpacing}px` }}>
<motion.div layout="position" className="flex items-center gap-2">
<div className={`font-bold ${flexLayout ? "" : "flex-1"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
<div className={`font-bold ${flexLayout ? "" : "flex-[1.5]"}`} style={{ fontSize: `${globalSettings?.subheaderSize || 16}px` }}>
{edu.school}
</div>
{centerSubtitle && (
@@ -45,7 +45,7 @@ const EducationSection = ({ education, globalSettings, showTitle = true }: Educa
{edu.gpa && ` · GPA ${edu.gpa}`}
</motion.div>
)}
{edu.description && (
{hasMeaningfulRichTextContent(edu.description) && (
<motion.div layout="position" className="mt-1 text-baseFont"
style={{ fontSize: `${globalSettings?.baseFontSize || 14}px`, lineHeight: globalSettings?.lineHeight || 1.6 }}
dangerouslySetInnerHTML={{ __html: normalizeRichTextContent(edu.description) }}
+20
View File
@@ -1,5 +1,8 @@
const HTML_TAG_REGEX = /<\/?[a-z][\s\S]*>/i;
const EMPTY_PARAGRAPH_REGEX = /<p>(?:\s|&nbsp;|<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 escapeHtml = (text: string) =>
text
@@ -23,3 +26,20 @@ export const normalizeRichTextContent = (content?: string) => {
return normalized.replace(EMPTY_PARAGRAPH_REGEX, "<p><br /></p>");
};
export const hasMeaningfulRichTextContent = (content?: string) => {
if (!content) return false;
if (!HTML_TAG_REGEX.test(content)) {
return content.replace(INVISIBLE_WHITESPACE_REGEX, "").length > 0;
}
const plainText = content
.replace(EMPTY_PARAGRAPH_REGEX, "")
.replace(HTML_BREAK_REGEX, "")
.replace(/&nbsp;/gi, " ")
.replace(HTML_ANY_TAG_REGEX, "")
.replace(INVISIBLE_WHITESPACE_REGEX, "");
return plainText.length > 0;
};