@memori.ai/memori-react
Version:
[](https://www.npmjs.com/package/@memori.ai/memori-react)   • 6.16 kB
text/typescript
import type { Medium } from '@memori.ai/memori-api-client/dist/types';
import {
officeExtensionShortLabels,
officeMimeShortLabels,
} from '../../helpers/constants';
import type { LinkPreviewInfo } from './MediaItemWidget.types';
export const FILE_EXTENSIONS_DARK_CARD = [
'TXT',
'HTML',
'PDF',
'DOC',
'DOCX',
'DOTX',
'XLS',
'XLSX',
'JSON',
'XML',
'MD',
'CSS',
'JS',
'TS',
'PY',
] as const;
export const FILE_MIME_TYPES_DARK_CARD = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/html',
'text/plain',
'text/css',
'text/javascript',
'application/json',
'application/xml',
'text/markdown',
] as const;
export const TEXT_FILE_EXTENSIONS = [
'TXT',
'HTML',
'MD',
'CSS',
'JS',
'TS',
'PY',
'JSON',
'XML',
] as const;
export const IMAGE_MIME_TYPES = [
'image/jpeg',
'image/png',
'image/jpg',
'image/gif',
] as const;
const MIME_TO_EXT: Record<string, string> = {
'application/pdf': 'PDF',
'text/html': 'HTML',
'text/plain': 'TXT',
'text/css': 'CSS',
'text/javascript': 'JS',
'application/json': 'JSON',
'application/xml': 'XML',
'text/markdown': 'MD',
};
export const FALLBACK_IMAGE_BASE64 =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2YwZjBmMCIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGZpbGw9IiM5OTk5OTkiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj5JbWFnZSBub3QgYXZhaWxhYmxlPC90ZXh0Pjwvc3ZnPg==';
export function formatBytes(bytes: number | undefined): string {
if (!bytes || bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
export function getFileExtensionFromUrl(
url: string | undefined
): string | null {
if (!url) return null;
const match = url.match(/\.([a-zA-Z0-9]+)(?:\?|$)/);
return match ? match[1].toUpperCase() : null;
}
function normalizeMimeType(mimeType: string): string {
return mimeType.split(';')[0].trim();
}
export function getFileExtensionFromMime(mimeType: string): string {
const normalized = normalizeMimeType(mimeType);
const normalizedLower = normalized.toLowerCase();
const officeLabel =
officeMimeShortLabels[normalized] ||
officeMimeShortLabels[normalizedLower];
if (officeLabel) return officeLabel;
return (
MIME_TO_EXT[normalized] ||
MIME_TO_EXT[normalizedLower] ||
MIME_TO_EXT[mimeType] ||
normalized.split('/')[1]?.toUpperCase() ||
'FILE'
);
}
export function getDocumentBadgeLabel(
mimeType: string,
filename?: string | null,
url?: string | null
): string {
const fromUrl = getFileExtensionFromUrl(url || undefined);
if (fromUrl) {
return officeExtensionShortLabels[fromUrl] || fromUrl;
}
const fromFilename = getFileExtensionFromUrl(filename || undefined);
if (fromFilename) {
return officeExtensionShortLabels[fromFilename] || fromFilename;
}
return getFileExtensionFromMime(mimeType);
}
export function countLines(content: string | undefined): number {
if (!content) return 0;
return content.split(/\r\n|\r|\n/).length;
}
export function shouldUseDarkFileCard(
_item: Medium,
fileExtension: string | null,
mimeType: string
): boolean {
if (
fileExtension &&
(FILE_EXTENSIONS_DARK_CARD as readonly string[]).includes(fileExtension)
) {
return true;
}
return (FILE_MIME_TYPES_DARK_CARD as readonly string[]).includes(mimeType);
}
const LINK_PREVIEW_BASE_URL = 'https://aisuru.com';
export async function fetchLinkPreview(
url: string,
baseUrl?: string
): Promise<LinkPreviewInfo | null> {
try {
const res = await fetch(
`${baseUrl || LINK_PREVIEW_BASE_URL}/api/linkpreview/${encodeURIComponent(url)}`
);
const data: LinkPreviewInfo = await res.json();
return data;
} catch (err) {
console.error('fetchLinkPreview', err);
return null;
}
}
export function getContentSize(item: Medium): number | undefined {
if (item.content != null) {
return new Blob([item.content]).size;
}
return item.properties?.size as number | undefined;
}
export function isValidUrl(urlString: string | undefined): boolean {
if (!urlString) return false;
try {
new URL(urlString);
return true;
} catch {
return false;
}
}
export function normalizeUrl(url: string | undefined): string | undefined {
if (!url || url.length === 0) return url;
return url.startsWith('http') ? url : `https://${url}`;
}
// ---------------------------------------------------------------------------
// Image source resolution (single source of truth for widget + modal)
// ---------------------------------------------------------------------------
export type ImageDisplaySource = {
/** Resolved URL or data URL for <img src>; undefined if no displayable source */
src: string | undefined;
/** True when item.url is a CSS color (rgb/rgba) — render as colored swatch, not <img> */
isRgb: boolean;
};
/**
* Resolves the display source for an image medium. Used by both the grid thumbnail
* and the preview modal so they stay in sync (resource URL, base64 content, or rgb/rgba).
*/
export function getImageDisplaySource(
item: Medium & { type?: string },
resourceUrl: string
): ImageDisplaySource {
const hasValidUrl =
isValidUrl(resourceUrl) || isValidUrl(item.url);
const isRgb =
!!item.url &&
(item.url.startsWith('rgb(') || item.url.startsWith('rgba('));
let src: string | undefined;
if (hasValidUrl) {
src = resourceUrl || item.url;
} else if (isRgb) {
src = item.url;
} else if (item.content) {
src = `data:${item.mimeType};base64,${item.content}`;
} else {
src = undefined;
}
return { src, isRgb };
}