first commit
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

This commit is contained in:
2026-03-04 06:30:47 +00:00
commit bb402d4ccc
777 changed files with 135661 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
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 [];
};