diff --git a/public/manifest.json b/public/manifest.json index b13c1ec..b92af7e 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -47,13 +47,39 @@ } ], "share_target": { - "action": "/", - "method": "GET", - "enctype": "application/x-www-form-urlencoded", + "action": "/share", + "method": "POST", + "enctype": "multipart/form-data", "params": { "title": "title", "text": "text", - "url": "url" + "url": "url", + "files": [ + { + "name": "files", + "accept": [ + "image/*", + "application/pdf", + "text/*", + "audio/*", + "video/*", + ".pdf", + ".jpg", + ".jpeg", + ".png", + ".webp", + ".gif", + ".svg", + ".enex", + ".md", + ".txt", + ".mp3", + ".m4a", + ".wav", + ".mp4" + ] + } + ] } }, "shortcuts": [ diff --git a/src/app/share/route.ts b/src/app/share/route.ts index b73306c..dca6cbb 100644 --- a/src/app/share/route.ts +++ b/src/app/share/route.ts @@ -321,11 +321,18 @@ export async function POST(req: NextRequest) { appendedMediaHtml += `
\n`; } else if (mimeType.startsWith('video/')) { appendedMediaHtml += `\n`; + } else if (mimeType === 'application/pdf' || filename.toLowerCase().endsWith('.pdf')) { + appendedMediaHtml += `\n`; } else { appendedMediaHtml += `\n`; } } + const firstImgAtt = await prisma.attachment.findFirst({ + where: { noteId: note.id, mimeType: { startsWith: 'image/' } }, + select: { id: true }, + }); + if (appendedMediaHtml) { const combinedHtml = `${finalHtml ? `${finalHtml}\n` : ''}${appendedMediaHtml}`; await prisma.note.update({ @@ -334,6 +341,7 @@ export async function POST(req: NextRequest) { contentHtml: combinedHtml, contentText: stripHtmlToText(combinedHtml), excerpt: makeExcerpt(stripHtmlToText(combinedHtml)), + ...(firstImgAtt ? { thumbnailUrl: `/api/attachments/${firstImgAtt.id}` } : {}), }, }); } diff --git a/src/lib/importers.ts b/src/lib/importers.ts index 1db95cd..a100f64 100644 --- a/src/lib/importers.ts +++ b/src/lib/importers.ts @@ -926,13 +926,14 @@ export async function clipUrlToNote( } ): PromiseSaved PDF document: ${escapeHtml(filename)}
\nSource: ${escapeHtml(url)}
`, + contentText: savedBlob.extractedText || resolvedTitle || filename, + excerpt: makeExcerpt(savedBlob.extractedText || resolvedTitle || filename), + notebookId, + sourceUrl: url, + isShortcut: options.isShortcut ?? false, + tags: { + create: tagIds.map((tagId) => ({ tagId })), + }, + }, + }); + + const att = await prisma.attachment.create({ + data: { + noteId: note.id, + filename, + mimeType: 'application/pdf', + sizeBytes: savedBlob.sizeBytes, + md5Hash: savedBlob.md5Hash, + storagePath: savedBlob.storagePath, + extractedText: savedBlob.extractedText, + }, + }); + + await prisma.note.update({ + where: { id: note.id }, + data: { + contentHtml: `Saved PDF document: ${escapeHtml(filename)}
\nSource: ${escapeHtml(url)}
`, + }, + }); + + try { + enqueueAttachmentOcr(att.id); + } catch {} + + try { + const { applyAutoSortToNote } = await import('./autoSort'); + await applyAutoSortToNote(note.id, userId); + } catch {} + + return note.id; + } + + // === CASE B: Direct Image clipping === + if (res && res.ok && isImage) { + const buf = Buffer.from(await res.arrayBuffer()); + const mimeType = contentType.startsWith('image/') ? contentType.split(';')[0].trim() : 'image/jpeg'; + let filename = ''; + try { + filename = decodeURIComponent(url.split('?')[0].split('/').pop() || 'image.jpg'); + } catch { + filename = 'image.jpg'; + } + const savedBlob = await saveAttachmentBuffer(buf, filename, mimeType); + const tagsToApply = [...(options.tags || ['inbox', 'web-clip'])]; + const tagIds = await getOrCreateTagIds(tagsToApply, userId); + + const resolvedTitle = + options.title && !options.title.startsWith('http') + ? options.title.trim() + : filename.replace(/\.[^.]+$/, '').replace(/[_-]+/g, ' ').trim(); + + const note = await prisma.note.create({ + data: { + userId, + title: resolvedTitle || 'Image Clip', + contentHtml: `Loading image...
`, + contentText: resolvedTitle || 'Image Clip', + excerpt: makeExcerpt(resolvedTitle || 'Image Clip'), + notebookId, + sourceUrl: url, + isShortcut: options.isShortcut ?? false, + tags: { + create: tagIds.map((tagId) => ({ tagId })), + }, + }, + }); + + const att = await prisma.attachment.create({ + data: { + noteId: note.id, + filename, + mimeType, + sizeBytes: savedBlob.sizeBytes, + md5Hash: savedBlob.md5Hash, + storagePath: savedBlob.storagePath, + extractedText: savedBlob.extractedText, + }, + }); + + await prisma.note.update({ + where: { id: note.id }, + data: { + contentHtml: `Source: ${escapeHtml(url)}
`, + thumbnailUrl: `/api/attachments/${att.id}`, + }, + }); + + try { + const { applyAutoSortToNote } = await import('./autoSort'); + await applyAutoSortToNote(note.id, userId); + } catch {} + + return note.id; + } + + let html = ''; + if (res) { + try { + html = await res.text(); + } catch { + html = `Saved link: ${escapeHtml(url)}
`; + } + } else { html = `Saved link: ${escapeHtml(url)}
`; } @@ -994,7 +1167,6 @@ export async function clipUrlToNote( contentHtml = `