feat: support captions for search results and sidebar for media
This commit is contained in:
@@ -11,10 +11,21 @@ interface PostSearchResult {
|
||||
interface MediaSearchResult {
|
||||
id: string;
|
||||
originalName: string;
|
||||
caption?: string;
|
||||
mimeType: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/** Get display name for media: caption (truncated to 60 chars) or fallback to filename */
|
||||
function getMediaDisplayName(media: MediaSearchResult): string {
|
||||
if (media.caption) {
|
||||
return media.caption.length > 60
|
||||
? media.caption.substring(0, 60) + '...'
|
||||
: media.caption;
|
||||
}
|
||||
return media.originalName;
|
||||
}
|
||||
|
||||
type SearchResult = PostSearchResult | MediaSearchResult;
|
||||
|
||||
type InsertMode = 'link' | 'image';
|
||||
@@ -251,7 +262,7 @@ export const InsertModal: React.FC<InsertModalProps> = ({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="insert-modal-result-title">{result.originalName}</div>
|
||||
<div className="insert-modal-result-title">{getMediaDisplayName(result)}</div>
|
||||
<div className="insert-modal-result-meta">
|
||||
{result.mimeType} • {new Date(result.createdAt).toLocaleDateString()}
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,16 @@ import { useAppStore, MediaData } from '../../store';
|
||||
import { showToast } from '../Toast';
|
||||
import './LinkedMediaPanel.css';
|
||||
|
||||
/** Get display name for media: caption (truncated to 60 chars) or fallback to filename */
|
||||
function getMediaDisplayName(media: MediaData): string {
|
||||
if (media.caption) {
|
||||
return media.caption.length > 60
|
||||
? media.caption.substring(0, 60) + '...'
|
||||
: media.caption;
|
||||
}
|
||||
return media.originalName;
|
||||
}
|
||||
|
||||
interface LinkedMediaPanelProps {
|
||||
postId: string;
|
||||
collapsed?: boolean;
|
||||
@@ -181,7 +191,12 @@ export const LinkedMediaPanel: React.FC<LinkedMediaPanelProps> = ({
|
||||
const unlinkedMedia = allMedia.filter(
|
||||
m => !linkedMedia.find(l => l.id === m.id)
|
||||
).filter(
|
||||
m => !mediaSearchQuery || m.originalName.toLowerCase().includes(mediaSearchQuery.toLowerCase())
|
||||
m => {
|
||||
if (!mediaSearchQuery) return true;
|
||||
const query = mediaSearchQuery.toLowerCase();
|
||||
return m.originalName.toLowerCase().includes(query) ||
|
||||
(m.caption && m.caption.toLowerCase().includes(query));
|
||||
}
|
||||
);
|
||||
|
||||
if (collapsed) {
|
||||
@@ -244,14 +259,14 @@ export const LinkedMediaPanel: React.FC<LinkedMediaPanelProps> = ({
|
||||
key={media.id}
|
||||
className="media-picker-item"
|
||||
onClick={() => handleLinkExisting(media.id)}
|
||||
title={media.originalName}
|
||||
title={media.caption || media.originalName}
|
||||
>
|
||||
{media.mimeType?.startsWith('image/') ? (
|
||||
<img src={`bds-media://${media.id}`} alt={media.originalName} />
|
||||
<img src={`bds-media://${media.id}`} alt={media.alt || media.originalName} />
|
||||
) : (
|
||||
<div className="media-icon">📄</div>
|
||||
)}
|
||||
<span className="media-name">{media.originalName}</span>
|
||||
<span className="media-name">{getMediaDisplayName(media)}</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
@@ -290,8 +305,8 @@ export const LinkedMediaPanel: React.FC<LinkedMediaPanelProps> = ({
|
||||
<div className="media-icon">📄</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="media-name" title={media.originalName}>
|
||||
{media.originalName}
|
||||
<span className="media-name" title={media.caption || media.originalName}>
|
||||
{getMediaDisplayName(media)}
|
||||
</span>
|
||||
<button
|
||||
className="unlink-btn"
|
||||
|
||||
@@ -4,6 +4,16 @@ import { showToast } from '../Toast';
|
||||
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
||||
import './Sidebar.css';
|
||||
|
||||
/** Get display name for media: caption (truncated to 60 chars) or fallback to filename */
|
||||
function getMediaDisplayName(media: MediaData): string {
|
||||
if (media.caption) {
|
||||
return media.caption.length > 60
|
||||
? media.caption.substring(0, 60) + '...'
|
||||
: media.caption;
|
||||
}
|
||||
return media.originalName;
|
||||
}
|
||||
|
||||
// Tag data with color information
|
||||
interface TagData {
|
||||
id: string;
|
||||
@@ -946,7 +956,7 @@ const MediaList: React.FC = () => {
|
||||
className={`media-item ${activeTabId === item.id ? 'selected' : ''}`}
|
||||
onClick={() => handleMediaClick(item.id)}
|
||||
onDoubleClick={() => handleMediaDoubleClick(item.id)}
|
||||
title={item.originalName}
|
||||
title={item.caption || item.originalName}
|
||||
>
|
||||
{item.mimeType.startsWith('image/') ? (
|
||||
<div className="media-thumbnail">
|
||||
@@ -967,7 +977,7 @@ const MediaList: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
<div className="media-item-info">
|
||||
<div className="media-item-name truncate">{item.originalName}</div>
|
||||
<div className="media-item-name truncate">{getMediaDisplayName(item)}</div>
|
||||
<div className="media-item-size">{formatFileSize(item.size)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user