/** * Generic file import for rNotes. * * Handles direct import of individual files: * - .md / .txt → parse as markdown/text * - .html → convert via Turndown * - .jpg / .png / .webp / .gif → create IMAGE note with stored file * * All produce ConvertedNote with sourceRef.source = 'manual'. */ import TurndownService from 'turndown'; import { markdownToTiptap, extractPlainTextFromTiptap } from './markdown-tiptap'; import { hashContent } from './index'; import type { ConvertedNote } from './index'; const turndown = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' }); /** Dispatch file import by extension / MIME type. */ export function importFile( filename: string, data: Uint8Array, mimeType?: string, ): ConvertedNote { const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase(); const textContent = () => new TextDecoder().decode(data); if (ext === '.md' || ext === '.markdown') { return importMarkdownFile(filename, textContent()); } if (ext === '.txt') { return importTextFile(filename, textContent()); } if (ext === '.html' || ext === '.htm') { return importHtmlFile(filename, textContent()); } if (['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp'].includes(ext)) { return importImageFile(filename, data, mimeType || guessMime(ext)); } // Default: treat as text try { return importTextFile(filename, textContent()); } catch { // Binary file — store as FILE note return importBinaryFile(filename, data, mimeType || 'application/octet-stream'); } } /** Import a markdown file. */ export function importMarkdownFile(filename: string, content: string): ConvertedNote { const title = titleFromFilename(filename); const tiptapJson = markdownToTiptap(content); const contentPlain = extractPlainTextFromTiptap(tiptapJson); return { title, content: tiptapJson, contentPlain, markdown: content, tags: [], sourceRef: { source: 'manual', externalId: `file:${filename}`, lastSyncedAt: Date.now(), contentHash: hashContent(content), }, }; } /** Import a plain text file — wrap as simple note. */ export function importTextFile(filename: string, content: string): ConvertedNote { const title = titleFromFilename(filename); const tiptapJson = markdownToTiptap(content); const contentPlain = content; return { title, content: tiptapJson, contentPlain, markdown: content, tags: [], sourceRef: { source: 'manual', externalId: `file:${filename}`, lastSyncedAt: Date.now(), contentHash: hashContent(content), }, }; } /** Import an HTML file — convert via Turndown. */ export function importHtmlFile(filename: string, html: string): ConvertedNote { const title = titleFromFilename(filename); const markdown = turndown.turndown(html); const tiptapJson = markdownToTiptap(markdown); const contentPlain = extractPlainTextFromTiptap(tiptapJson); return { title, content: tiptapJson, contentPlain, markdown, tags: [], sourceRef: { source: 'manual', externalId: `file:${filename}`, lastSyncedAt: Date.now(), contentHash: hashContent(markdown), }, }; } /** Import an image file — create IMAGE note with stored file reference. */ export function importImageFile(filename: string, data: Uint8Array, mimeType: string): ConvertedNote { const title = titleFromFilename(filename); const md = `![${title}](/data/files/uploads/${filename})`; const tiptapJson = markdownToTiptap(md); return { title, content: tiptapJson, contentPlain: title, markdown: md, tags: [], type: 'IMAGE', attachments: [{ filename, data, mimeType }], sourceRef: { source: 'manual', externalId: `file:${filename}`, lastSyncedAt: Date.now(), contentHash: hashContent(String(data.length)), }, }; } /** Import a binary/unknown file as a FILE note. */ function importBinaryFile(filename: string, data: Uint8Array, mimeType: string): ConvertedNote { const title = titleFromFilename(filename); const md = `[${filename}](/data/files/uploads/${filename})`; const tiptapJson = markdownToTiptap(md); return { title, content: tiptapJson, contentPlain: title, markdown: md, tags: [], type: 'FILE', attachments: [{ filename, data, mimeType }], sourceRef: { source: 'manual', externalId: `file:${filename}`, lastSyncedAt: Date.now(), contentHash: hashContent(String(data.length)), }, }; } function titleFromFilename(filename: string): string { return filename.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' '); } function guessMime(ext: string): string { const mimes: Record = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml', '.bmp': 'image/bmp', }; return mimes[ext] || 'application/octet-stream'; }