172 lines
4.7 KiB
TypeScript
172 lines
4.7 KiB
TypeScript
/**
|
|
* 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 = ``;
|
|
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<string, string> = {
|
|
'.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';
|
|
}
|