Skip to content

Commit

Permalink
✨ feat: support nested italic / bold / link text
Browse files Browse the repository at this point in the history
  • Loading branch information
RylanBot committed Jul 12, 2024
1 parent 5d4b569 commit 644173d
Show file tree
Hide file tree
Showing 14 changed files with 451 additions and 599 deletions.
4 changes: 2 additions & 2 deletions src/components/layout/SettingEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BsImageFill } from "react-icons/bs";
import { RiSave3Fill } from "react-icons/ri";
import { TbLocationCancel } from "react-icons/tb";

import { formatTitle } from "@/helpers/TextParser";
import { formatTitle } from "@/helpers/CommonUtils";

import { StyleData } from "@/types/style";

Expand Down Expand Up @@ -69,7 +69,7 @@ const SettingEditor: React.FC = () => {

return (
<>
<div className="fixed top-16 z-5 w-80 overflow-y-auto max-h-screen px-4 py-2 transition-all duration-500 ease-in-out">
<div className="fixed z-20 top-16 w-80 overflow-y-auto max-h-screen px-4 py-2 transition-all duration-500 ease-in-out">
<button onClick={handleConfirmEdit} className="setting-button fixed top-4 left-6 w-20">
<div className="flex items-center">
<RiSave3Fill className="mr-2" />
Expand Down
8 changes: 4 additions & 4 deletions src/components/templates/ExperienceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const ExperienceCard: React.FC<ExperienceCardProp> = ({
<>
{!lineBreak && <span className='font-normal text-gray-400 theme-divider-color mx-1 align-middle'></span>}
<span ref={subtitleRef} className='align-middle'>
<EditableText type={'link'} text={subtitle} path={`experience.${sectionIndex}.items[${itemIndex}].subtitle`}
<EditableText text={subtitle} path={`experience.${sectionIndex}.items[${itemIndex}].subtitle`}
className='theme-text-color font-semibold text-details '
/>
</span>
Expand All @@ -83,13 +83,13 @@ const ExperienceCard: React.FC<ExperienceCardProp> = ({
)}
</span>
</div>
<p className='mb-1'>
{tech && <EditableText type={'tech'} text={tech} path={`experience.${sectionIndex}.items[${itemIndex}].tech`} />}
<p className='mb-1 theme-text-color'>
{tech && <EditableText text={tech} path={`experience.${sectionIndex}.items[${itemIndex}].tech`} />}
</p>
<ul className='list-disc list-inside mb-2'>
{details?.filter(detail => detail.trim()).map((detail, detailIndex) => (
<li key={detailIndex} className='theme-marker-color font-normal text-details'>
<EditableText type={'strong'} text={detail} path={`experience.${sectionIndex}.items[${itemIndex}].details[${detailIndex}]`} />
<EditableText text={detail} path={`experience.${sectionIndex}.items[${itemIndex}].details[${detailIndex}]`} />
</li>
))}
</ul>
Expand Down
219 changes: 115 additions & 104 deletions src/components/templates/ProfileList.tsx
Original file line number Diff line number Diff line change
@@ -1,123 +1,134 @@
import React from 'react';
import React from "react";

import { ProfileData } from '@/types/profile';

import EditableText from '@/components/toolkit/EditableText';
import EditableText from "@/components/toolkit/EditableText";
import { ProfileData } from "@/types/profile";

interface ProfileListProps {
data: ProfileData;
theme?: string
data: ProfileData;
theme?: string;
}

const ProfileListAvatar: React.FC<ProfileListProps> = ({ data }) => {
return (
<div className='flex justify-between items-center custom-profile'>

<div className='flex items-center'>
{data.avatar && (
<div className='w-[80px] h-[100px] mr-6'>
<img src={data.avatar} className='w-full h-full' />
</div>
)}
<div className='ml-2'>
<p className='text-xl mt-1 mb-3 theme-text-color'>
{data.name && <EditableText text={data.name} path={`profile.name`} className={'font-bold'} />}
</p>
{data.footnote?.map((footnote, footIndex) => (
<p className='text-sm mt-2' key={footIndex}>
<EditableText text={footnote.label} path={`profile.footnote[${footIndex}].label`}
className='font-semibold theme-text-color'
/>
{footnote.label && footnote.content && <span>:&nbsp;</span>}
<EditableText text={footnote.content} path={`profile.footnote[${footIndex}].content`}
className='font-normal'
/>
</p>
))}
</div>
</div>

<div className='flex flex-col ml-20 mt-2 text-[12.75px]'>
{data.contact?.map((contact, conIndex) => (
<p className='flex' key={conIndex}>
{contact.icon &&
<EditableText type={'icon'} text={contact.icon.trim()} path={`profile.contact[${conIndex}].icon`}
className='w-4 h-4 mr-1 theme-text-color'
/>}
{contact.key &&
<span className='theme-text-color mt-0.5'>
<EditableText text={contact.key} path={`profile.contact[${conIndex}].key`} className='font-bold' />
:&nbsp;
</span>
}
{contact.value && <EditableText type={'link'} text={contact.value} path={`profile.contact[${conIndex}].value`} />}
</p>
))}
</div>

return (
<div className="flex justify-between items-center custom-profile">
<div className="flex items-center">
{data.avatar && (
<div className="w-[80px] h-[100px] mr-6">
<img src={data.avatar} className="w-full h-full" />
</div>
)}
<div className="ml-2">
<p className="text-xl mt-1 mb-3 theme-text-color">
{data.name && (
<EditableText
text={data.name}
path={`profile.name`}
className={"font-bold"}
/>
)}
</p>
{data.footnote?.map((note, noteIndex) => (
<p className="text-sm mt-2" key={noteIndex}>
<EditableText
text={note}
path={`profile.footnote[${noteIndex}].label`}
/>
</p>
))}
</div>
);
</div>

<div className="flex flex-col ml-20 mt-2 text-[12.75px]">
{data.contact?.map((contact, conIndex) => (
<p className="flex" key={conIndex}>
{contact.icon && (
<EditableText
type={"icon"}
text={contact.icon.trim()}
path={`profile.contact[${conIndex}].icon`}
className="w-4 h-4 mr-1 theme-text-color"
/>
)}
{contact.value && (
<EditableText
text={contact.value}
path={`profile.contact[${conIndex}].value`}
/>
)}
</p>
))}
</div>
</div>
);
};

const ProfileListPlain: React.FC<ProfileListProps> = ({ data }) => {
return (
<div className='custom-profile'>

<div className='flex-grow flex-shrink'>
<p className='text-xl font-bold text-gray-800 text-center mt-2 theme-text-color'>
<EditableText text={data.name} path={`profile.name`} />
</p>

{data.footnote && data?.footnote?.length > 0 && (
<div className='flex flex-wrap justify-center items-center text-sm gap-1 mt-1'>
{data.footnote?.map((footnote, footIndex) => (
<div className='flex justify-center items-center plain-footnote-item' key={footIndex} style={{ flexBasis: 'auto' }}>
<div className='flex justify-center items-center'>
<EditableText text={footnote.label} path={`profile.footnote[${footIndex}].label`}
className='font-semibold theme-text-color' />
{footnote.label && footnote.content && <span className='theme-text-color'>:&nbsp;</span>}
{footnote.content && <EditableText text={footnote.content} path={`profile.footnote[${footIndex}].content`} />}
</div>
</div>
))}
</div>
)}
return (
<div className="custom-profile">
<div className="flex-grow flex-shrink">
<p className="text-xl font-bold text-gray-800 text-center mt-2 theme-text-color">
{data.name && <EditableText text={data.name} path={`profile.name`} />}
</p>

<div className='flex flex-wrap justify-center items-center text-[13.5px] mt-1'>
{data.contact?.map((item, index) => (
<div className='flex justify-center items-center plain-contact-item' key={index} style={{ flexBasis: 'auto' }}>
<div className='flex items-center'>
{item.icon &&
<EditableText type={'icon'} text={item.icon.trim()} path={`profile.contact[${index}].icon`}
className='w-4 h-4 mr-0.5 theme-text-color'
/>
}
{item.key && (
<span className='theme-text-color'>
<EditableText text={item.key} path={`profile.contact[${index}].key`} className={'font-bold '} />
:&nbsp;
</span>
)}
{item.value && <EditableText type={'link'} text={item.value} path={`profile.contact[${index}].value`} />}
</div>
</div>
))}
{data.footnote && data.footnote?.length > 0 && (
<div className="flex flex-wrap justify-center items-center text-sm gap-1 mt-1">
{data.footnote?.map((note, noteIndex) => (
<div
className="flex justify-center items-center plain-footnote-item"
key={noteIndex}
style={{ flexBasis: "auto" }}
>
<div className="flex justify-center items-center">
<EditableText
text={note}
path={`profile.footnote[${noteIndex}].label`}
/>
</div>
</div>
))}
</div>
)}

<div className="flex flex-wrap justify-center items-center text-[13.5px] mt-1">
{data.contact?.map((item, index) => (
<div
className="flex justify-center items-center plain-contact-item"
key={index}
style={{ flexBasis: "auto" }}
>
<div className="flex items-center">
{item.icon && (
<EditableText
type={"icon"}
text={item.icon.trim()}
path={`profile.contact[${index}].icon`}
className="w-4 h-4 mr-0.5 theme-text-color"
/>
)}
{item.value && (
<EditableText
text={item.value}
path={`profile.contact[${index}].value`}
/>
)}
</div>
</div>
</div >
);
))}
</div>
</div>
</div>
);
};

const ProfileList: React.FC<ProfileListProps> = ({ data, theme }) => {
switch (theme) {
case 'avatar':
return <ProfileListAvatar data={data} />;
case 'plain':
return <ProfileListPlain data={data} />;
default:
return <ProfileListAvatar data={data} />;
}
switch (theme) {
case "avatar":
return <ProfileListAvatar data={data} />;
case "plain":
return <ProfileListPlain data={data} />;
default:
return <ProfileListAvatar data={data} />;
}
};

export default ProfileList;
export default ProfileList;
18 changes: 5 additions & 13 deletions src/components/toolkit/EditableText.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React, { useEffect, useRef, useState } from 'react';

// import IconParser from '@/helpers/IconParser';
import { LinkParser, StrongTextParser, TechParser } from '@/helpers/TextParser';
import TextParser from '@/helpers/TextParser';
import useEditWithUndo from '@/hooks/useEditWithUndo';
import useModeStore from '@/stores/modeStore';

interface EditableTextProps {
/* 不传类型则默认渲染纯文本 */
type?: "strong" | "link" | "tech" | "icon";
text?: string;
type?: "icon";
text: string;
/* 索引路径,比如 profile.name,experience.[0].item[1].details[2] */
path: string;
className?: string;
Expand All @@ -31,17 +30,10 @@ const EditableText: React.FC<EditableTextProps> = (
return rawText;
} else {
switch (type) {
case 'strong':
return <>{StrongTextParser(rawText ?? '')}</>;
case 'link':
return <>{LinkParser(rawText ?? '', className)}</>;
case 'tech':
return <>{TechParser(rawText ?? '')}</>;
case 'icon':
// return <>{(IconParser(rawText?.trim() ?? '', className))}</>
return <> <i id="contact-icon" className={`${rawText} ${className}`}></i></>
return <i id="contact-icon" className={`${rawText ?? ""} ${className}`}></i>;
default:
return <span className={className}>{rawText}</span>;
return <>{TextParser(rawText ?? "", className)}</>;
}
}
};
Expand Down
Loading

0 comments on commit 644173d

Please sign in to comment.