Some checks failed
Backend Tests / Static Checks (push) Has been cancelled
Backend Tests / Tests (other) (push) Has been cancelled
Backend Tests / Tests (plugin) (push) Has been cancelled
Backend Tests / Tests (server) (push) Has been cancelled
Backend Tests / Tests (store) (push) Has been cancelled
Build Canary Image / build-frontend (push) Has been cancelled
Build Canary Image / build-push (linux/amd64) (push) Has been cancelled
Build Canary Image / build-push (linux/arm64) (push) Has been cancelled
Build Canary Image / merge (push) Has been cancelled
Frontend Tests / Lint (push) Has been cancelled
Frontend Tests / Build (push) Has been cancelled
Proto Linter / Lint Protos (push) Has been cancelled
130 lines
4.5 KiB
TypeScript
130 lines
4.5 KiB
TypeScript
import { useMemo } from "react";
|
|
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
|
|
import { getAttachmentUrl } from "@/utils/attachment";
|
|
|
|
interface PositionedImageViewProps {
|
|
imageId: string;
|
|
className?: string;
|
|
style?: React.CSSProperties | { cssText: string };
|
|
alt?: string;
|
|
}
|
|
|
|
// Simplified positioned image view for memo display (read-only)
|
|
export const PositionedImageView = ({ imageId, className, style, alt }: PositionedImageViewProps) => {
|
|
// Debug: log component props
|
|
// console.log('PositionedImageView props:', { imageId, style });
|
|
// Construct the URL directly using Memos standard URL structure
|
|
// Format: /file/{attachment-name}/{filename}
|
|
const imageUrl = useMemo(() => {
|
|
// Extract filename from imageId if it contains path info
|
|
// For simple IDs, we'll use a generic filename
|
|
const filename = imageId.includes('/') ? imageId.split('/').pop() || 'image' : 'image.jpg';
|
|
return `${window.location.origin}/file/${imageId}/${filename}`;
|
|
}, [imageId]);
|
|
|
|
const containerStyle = useMemo(() => {
|
|
const baseStyles: React.CSSProperties = {
|
|
display: 'block',
|
|
};
|
|
|
|
// Handle different style input types
|
|
if (style) {
|
|
if ('cssText' in style) {
|
|
// Handle string-based styles
|
|
const styleStr = style.cssText;
|
|
if (styleStr.includes('text-align: left')) baseStyles.textAlign = 'left';
|
|
if (styleStr.includes('text-align: center')) baseStyles.textAlign = 'center';
|
|
if (styleStr.includes('text-align: right')) baseStyles.textAlign = 'right';
|
|
if (styleStr.includes('float: left')) baseStyles.float = 'left';
|
|
if (styleStr.includes('float: right')) baseStyles.float = 'right';
|
|
|
|
// Handle margin - only set if not already defined
|
|
if (styleStr.includes('margin:') && !('margin' in baseStyles)) {
|
|
const marginMatch = styleStr.match(/margin:\s*([^;]+)/);
|
|
if (marginMatch) baseStyles.margin = marginMatch[1].trim();
|
|
}
|
|
|
|
// Set default margin-bottom only if no margin is specified
|
|
if (!styleStr.includes('margin:') && !('margin' in baseStyles) && !('marginBottom' in baseStyles)) {
|
|
baseStyles.marginBottom = '1rem';
|
|
}
|
|
} else {
|
|
// Handle CSSProperties object
|
|
Object.assign(baseStyles, style);
|
|
|
|
// Set default margin-bottom only if no margin properties exist
|
|
if (!('margin' in baseStyles) && !('marginBottom' in baseStyles)) {
|
|
baseStyles.marginBottom = '1rem';
|
|
}
|
|
}
|
|
}
|
|
|
|
return baseStyles;
|
|
}, [style]);
|
|
|
|
const imageStyle = useMemo(() => {
|
|
const baseStyles: React.CSSProperties = {
|
|
maxWidth: '100%',
|
|
height: 'auto',
|
|
borderRadius: '0.5rem',
|
|
};
|
|
|
|
// Extract width/height from style if present
|
|
if (style) {
|
|
if ('cssText' in style) {
|
|
const styleStr = style.cssText;
|
|
const widthMatch = styleStr.match(/width:\s*([^;]+)/);
|
|
const heightMatch = styleStr.match(/height:\s*([^;]+)/);
|
|
|
|
if (widthMatch) baseStyles.width = widthMatch[1].trim();
|
|
if (heightMatch) baseStyles.height = heightMatch[1].trim();
|
|
}
|
|
}
|
|
|
|
return baseStyles;
|
|
}, [style]);
|
|
|
|
// Debug: log image URL
|
|
// console.log('Image URL:', imageUrl);
|
|
|
|
// Fallback to showing the tag if image URL cannot be constructed
|
|
if (!imageUrl || imageUrl.includes('undefined')) {
|
|
return (
|
|
<span className="inline-block bg-yellow-100 text-yellow-800 px-1 rounded text-sm">
|
|
[img:{imageId}]
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={`positioned-image-wrapper ${className || ''}`}
|
|
style={containerStyle}
|
|
data-img-id={imageId}
|
|
>
|
|
<img
|
|
src={imageUrl}
|
|
alt={alt || `Image ${imageId}`}
|
|
style={imageStyle}
|
|
className="rounded-lg shadow-sm"
|
|
loading="lazy"
|
|
onError={(e) => {
|
|
// Fallback if image fails to load
|
|
const target = e.target as HTMLImageElement;
|
|
target.style.display = 'none';
|
|
const fallback = document.createElement('span');
|
|
fallback.className = 'inline-block bg-red-100 text-red-800 px-1 rounded text-sm';
|
|
fallback.textContent = `[img:${imageId}] (failed to load)`;
|
|
target.parentNode?.appendChild(fallback);
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Alternative approach: Create a hook to get attachments for the current memo
|
|
export const useMemoAttachments = () => {
|
|
// This would need to be implemented based on how memos are loaded
|
|
// For now, return empty array
|
|
return [];
|
|
}; |