72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
|
|
export interface MentionImageOption {
|
||
|
|
id: string;
|
||
|
|
src: string;
|
||
|
|
name: string;
|
||
|
|
label?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getImageMentionQuery(value: string, selectionStart: number | null | undefined): string | null {
|
||
|
|
if (selectionStart === null || selectionStart === undefined) return null;
|
||
|
|
const beforeCursor = value.slice(0, selectionStart);
|
||
|
|
const match = beforeCursor.match(/(?:^|\s)@([^\s@]*)$/);
|
||
|
|
return match ? match[1] ?? "" : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function insertImageMentionValue(
|
||
|
|
value: string,
|
||
|
|
selectionStart: number,
|
||
|
|
imageName: string,
|
||
|
|
maxLength: number,
|
||
|
|
): { value: string; selectionStart: number } {
|
||
|
|
const beforeCursor = value.slice(0, selectionStart);
|
||
|
|
const afterCursor = value.slice(selectionStart);
|
||
|
|
const match = beforeCursor.match(/(?:^|\s)@([^\s@]*)$/);
|
||
|
|
const mentionStart = match ? beforeCursor.length - match[0].length + (match[0].startsWith("@") ? 0 : 1) : selectionStart;
|
||
|
|
const mentionText = `@${imageName.trim() || "uploaded-image"} `;
|
||
|
|
const nextValue = `${value.slice(0, mentionStart)}${mentionText}${afterCursor}`.slice(0, maxLength);
|
||
|
|
const nextSelectionStart = Math.min(mentionStart + mentionText.length, nextValue.length);
|
||
|
|
return { value: nextValue, selectionStart: nextSelectionStart };
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ImageMentionMenuProps {
|
||
|
|
images: MentionImageOption[];
|
||
|
|
query?: string;
|
||
|
|
onSelect: (image: MentionImageOption) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
function ImageMentionMenu({ images, query = "", onSelect }: ImageMentionMenuProps) {
|
||
|
|
const normalizedQuery = query.trim().toLowerCase();
|
||
|
|
const visibleImages = normalizedQuery
|
||
|
|
? images.filter((image) => `${image.label ?? ""} ${image.name}`.toLowerCase().includes(normalizedQuery))
|
||
|
|
: images;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="image-mention-menu" role="listbox" aria-label="选择上传图片">
|
||
|
|
{visibleImages.length ? (
|
||
|
|
visibleImages.map((image) => (
|
||
|
|
<button
|
||
|
|
key={image.id}
|
||
|
|
type="button"
|
||
|
|
className="image-mention-menu__item"
|
||
|
|
role="option"
|
||
|
|
onMouseDown={(event) => {
|
||
|
|
event.preventDefault();
|
||
|
|
onSelect(image);
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<img src={image.src} alt="" />
|
||
|
|
<span>
|
||
|
|
<strong>{image.label ?? image.name}</strong>
|
||
|
|
<em>{image.name}</em>
|
||
|
|
</span>
|
||
|
|
</button>
|
||
|
|
))
|
||
|
|
) : (
|
||
|
|
<span className="image-mention-menu__empty">没有匹配的上传图片</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export default ImageMentionMenu;
|