feat: add PDF export functionality (#3264)

* feature add: export article as pdf (issue: 1314)

* reverted pnpm-lock.yaml, deleted unwanted code, ensured some checks

* chore: update icon

* update changelog

---------

Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
This commit is contained in:
Vikas 2025-03-25 06:40:38 +05:30 committed by GitHub
parent 831028a071
commit 1a9f273606
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 108 additions and 0 deletions

View File

@ -3,6 +3,7 @@
## New Features
- New Action Language setting
- Export entry content to a PDF file
## Improvements

View File

@ -15,6 +15,8 @@
"discover.rss_url": "رابط RSS",
"discover.select_placeholder": "اختر",
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.exported_notify": "تم تصدير المقال كملف PDF.",
"entry_actions.export_as_pdf": "تصدير كملف PDF",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل في الحفظ إلى Obsidian.",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "رابط RSS",
"discover.select_placeholder": "اختر",
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.exported_notify": "تم تصدير المقال كملف PDF.",
"entry_actions.export_as_pdf": "تصدير كملف PDF",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل في الحفظ إلى Obsidian.",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "رابط RSS",
"discover.select_placeholder": "اختر",
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.exported_notify": "تم تصدير المقال كملف PDF.",
"entry_actions.export_as_pdf": "تصدير كملف PDF",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل في الحفظ إلى Obsidian",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "رابط RSS",
"discover.select_placeholder": "اختر",
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.exported_notify": "تم تصدير المقال كملف PDF.",
"entry_actions.export_as_pdf": "تصدير كملف PDF",
"entry_actions.failed_to_save_to_eagle": "فشل الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل الحفظ في Obsidian",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "عنوان URL لخلاصة RSS",
"discover.select_placeholder": "اختيار",
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.exported_notify": "تم تصدير المقال كملف PDF.",
"entry_actions.export_as_pdf": "تصدير كملف PDF",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_readwise": "فشل في الحفظ إلى Readwise.",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "رابط RSS",
"discover.select_placeholder": "اختر",
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.exported_notify": "تم تصدير المقال كملف PDF.",
"entry_actions.export_as_pdf": "تصدير كملف PDF",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_readwise": "فشل في الحفظ إلى Readwise.",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "RSS-URL",
"discover.select_placeholder": "Auswählen",
"entry_actions.copy_link": "Link kopieren",
"entry_actions.exported_notify": "Artikel als PDF exportiert.",
"entry_actions.export_as_pdf": "Als PDF exportieren",
"entry_actions.failed_to_save_to_eagle": "Speichern in Eagle fehlgeschlagen.",
"entry_actions.failed_to_save_to_instapaper": "Speichern in Instapaper fehlgeschlagen.",
"entry_actions.failed_to_save_to_obsidian": "Speichern in Obsidian fehlgeschlagen",

View File

@ -108,6 +108,8 @@
"entry_actions.image_gallery": "Image Gallery",
"entry_actions.copied_notify": "{{which}} copied to clipboard.",
"entry_actions.copy_link": "Copy Link",
"entry_actions.exported_notify": "Exported article as PDF.",
"entry_actions.export_as_pdf": "Export as PDF",
"entry_actions.copy_title": "Copy Title",
"entry_actions.delete": "Delete",
"entry_actions.deleted": "Deleted.",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "RSS URL",
"discover.select_placeholder": "Seleccionar",
"entry_actions.copy_link": "Copiar enlace",
"entry_actions.exported_notify": "Artículo exportado como PDF.",
"entry_actions.export_as_pdf": "Exportar como PDF",
"entry_actions.failed_to_save_to_obsidian": "Error al guardar en Obsidian",
"entry_actions.mark_as_read": "Marcar como leído",
"entry_actions.mark_as_unread": "Marcar como no leído",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "RSS-URL",
"discover.select_placeholder": "Valitse",
"entry_actions.copy_link": "Kopioi linkki",
"entry_actions.exported_notify": "Artikkeli vietiin PDF-muodossa.",
"entry_actions.export_as_pdf": "Vie PDF:nä",
"entry_actions.failed_to_save_to_eagle": "Tallennus Eagleen epäonnistui.",
"entry_actions.failed_to_save_to_instapaper": "Tallennus Instapaperiin epäonnistui.",
"entry_actions.failed_to_save_to_readwise": "Tallennus Readwiseen epäonnistui.",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "URL du RSS",
"discover.select_placeholder": "Sélectionner",
"entry_actions.copy_link": "Copier le lien",
"entry_actions.exported_notify": "Article exporté en PDF.",
"entry_actions.export_as_pdf": "Exporter en PDF",
"entry_actions.failed_to_save_to_eagle": "Échec de l'enregistrement sur Eagle.",
"entry_actions.failed_to_save_to_instapaper": "Échec de l'enregistrement sur Instapaper.",
"entry_actions.failed_to_save_to_readwise": "Échec de l'enregistrement sur Readwise.",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "URL RSS",
"discover.select_placeholder": "Seleziona",
"entry_actions.copy_link": "Copia link",
"entry_actions.exported_notify": "Articolo esportato come PDF.",
"entry_actions.export_as_pdf": "Esporta come PDF",
"entry_actions.failed_to_save_to_eagle": "Salvataggio su Eagle non riuscito.",
"entry_actions.failed_to_save_to_instapaper": "Salvataggio su Instapaper non riuscito.",
"entry_actions.failed_to_save_to_readwise": "Salvataggio su Readwise non riuscito.",

View File

@ -107,6 +107,8 @@
"discover.target.lists": "リスト",
"entry_actions.copied_notify": "{{which}} をクリップボードにコピーしました。",
"entry_actions.copy_link": "リンクをコピー",
"entry_actions.exported_notify": "記事をPDFとしてエクスポートしました。",
"entry_actions.export_as_pdf": "PDFとしてエクスポート",
"entry_actions.copy_title": "タイトルをコピー",
"entry_actions.delete": "削除",
"entry_actions.deleted": "削除済み",

View File

@ -107,6 +107,8 @@
"discover.target.lists": "목록",
"entry_actions.copied_notify": "{{which}}이(가) 클립보드에 복사되었습니다",
"entry_actions.copy_link": "링크 복사",
"entry_actions.exported_notify": "PDF로 기사 내보내기 완료.",
"entry_actions.export_as_pdf": "PDF로 내보내기",
"entry_actions.copy_title": "제목 복사",
"entry_actions.delete": "삭제",
"entry_actions.deleted": "삭제됨",

View File

@ -15,6 +15,8 @@
"discover.rss_url": "RSS URL",
"discover.select_placeholder": "選択",
"entry_actions.copy_link": "リンクをコピー",
"entry_actions.exported_notify": "Artigo exportado como PDF.",
"entry_actions.export_as_pdf": "Exportar como PDF",
"entry_actions.failed_to_save_to_eagle": "Eagle への保存に失敗しました。",
"entry_actions.failed_to_save_to_instapaper": "Instapaper への保存に失敗しました。",
"entry_actions.failed_to_save_to_readwise": "Readwise への保存に失敗しました。",

View File

@ -103,6 +103,8 @@
"discover.target.lists": "Списки",
"entry_actions.copied_notify": "{{which}} скопировано в буфер обмена.",
"entry_actions.copy_link": "Копировать ссылку",
"entry_actions.exported_notify": "Статья экспортирована в PDF.",
"entry_actions.export_as_pdf": "Экспортировать в PDF",
"entry_actions.copy_title": "Копировать заголовок",
"entry_actions.delete": "Удалить",
"entry_actions.deleted": "Удалено.",

View File

@ -28,6 +28,8 @@
"discover.target.label": "Arama hedefi",
"discover.target.lists": "Listeler",
"entry_actions.copy_link": "Bağlantıyı kopyala",
"entry_actions.exported_notify": "Makale PDF olarak dışa aktarıldı.",
"entry_actions.export_as_pdf": "PDF olarak dışa aktar",
"entry_actions.failed_to_save_to_eagle": "Eagle'a kaydetme başarısız oldu.",
"entry_actions.failed_to_save_to_instapaper": "Instapaper'a kaydetme başarısız oldu.",
"entry_actions.failed_to_save_to_readwise": "Readwise'a kaydetme başarısız oldu.",

View File

@ -108,6 +108,8 @@
"entry_actions.image_gallery": "图片库",
"entry_actions.copied_notify": "{{which}}已复制到剪贴板。",
"entry_actions.copy_link": "复制链接",
"entry_actions.exported_notify": "文章已导出为 PDF。",
"entry_actions.export_as_pdf": "导出为 PDF",
"entry_actions.copy_title": "复制标题",
"entry_actions.delete": "删除",
"entry_actions.deleted": "已删除",

View File

@ -108,6 +108,8 @@
"entry_actions.image_gallery": "圖片庫",
"entry_actions.copied_notify": "{{which}}已複製到剪貼簿",
"entry_actions.copy_link": "複製連結",
"entry_actions.exported_notify": "文章已匯出為 PDF。",
"entry_actions.export_as_pdf": "匯出為 PDF",
"entry_actions.copy_title": "複製標題",
"entry_actions.delete": "刪除",
"entry_actions.deleted": "已刪除",

View File

@ -104,6 +104,8 @@
"discover.target.lists": "列表",
"entry_actions.copied_notify": "{{which}}已複製到剪貼簿",
"entry_actions.copy_link": "複製連結",
"entry_actions.exported_notify": "文章已匯出為 PDF。",
"entry_actions.export_as_pdf": "匯出為 PDF",
"entry_actions.copy_title": "複製標題",
"entry_actions.delete": "刪除",
"entry_actions.deleted": "已刪除。",

View File

@ -146,6 +146,10 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee
hide: !entry?.entries.url,
shortcut: shortcuts.entry.copyLink.key,
},
{
id: COMMAND_ID.entry.exportAsPDF,
onClick: runCmdFn(COMMAND_ID.entry.exportAsPDF, [{ entryId }]),
},
{
id: COMMAND_ID.entry.openInBrowser,
onClick: runCmdFn(COMMAND_ID.entry.openInBrowser, [{ entryId }]),

View File

@ -175,6 +175,25 @@ export const useRegisterEntryCommands = () => {
})
},
},
{
id: COMMAND_ID.entry.exportAsPDF,
label: t("entry_actions.export_as_pdf"),
icon: <i className="i-mgc-pdf-cute-re" />,
run: ({ entryId }) => {
const entry = useEntryStore.getState().flatMapEntries[entryId]
if (!entry) {
toast.error("Failed to export as pdf: entry is not available", { duration: 3000 })
return
}
window.print()
toast(t("entry_actions.exported_notify"), {
duration: 1000,
})
},
},
{
id: COMMAND_ID.entry.copyTitle,
label: t("entry_actions.copy_title"),

View File

@ -11,6 +11,7 @@ export const COMMAND_ID = {
read: "entry:read",
toggleAISummary: "entry:toggle-ai-summary",
toggleAITranslation: "entry:toggle-ai-translation",
exportAsPDF: "entry:export-as-pdf",
},
integration: {
saveToEagle: "integration:save-to-eagle",

View File

@ -25,6 +25,11 @@ export type CopyLinkCommand = Command<{
fn: (data: { entryId: string }) => void
}>
export type ExportAsPDFCommand = Command<{
id: typeof COMMAND_ID.entry.exportAsPDF
fn: (data: { entryId: string }) => void
}>
export type CopyTitleCommand = Command<{
id: typeof COMMAND_ID.entry.copyTitle
fn: (data: { entryId: string }) => void
@ -65,6 +70,7 @@ export type EntryCommand =
| StarCommand
| DeleteCommand
| CopyLinkCommand
| ExportAsPDFCommand
| CopyTitleCommand
| OpenInBrowserCommand
| ViewSourceContentCommand

View File

@ -21,6 +21,7 @@ export const DEFAULT_ACTION_ORDER: ToolbarActionOrder = {
[
COMMAND_ID.entry.copyLink,
COMMAND_ID.entry.openInBrowser,
COMMAND_ID.entry.exportAsPDF,
COMMAND_ID.entry.read,
] as string[]
).includes(id),

View File

@ -2,3 +2,13 @@
@import "./additional.css";
@import "./scrollbar.css";
@import "./cursor.css";
@media print {
* {
overflow: visible !important;
page-break-after: avoid;
page-break-before: avoid;
break-inside: avoid;
height: max-content;
}
}

View File

@ -0,0 +1,25 @@
import * as React from "react"
import Svg, { Path } from "react-native-svg"
interface PdfCuteReIconProps {
width?: number
height?: number
color?: string
}
export const PdfCuteReIcon = ({
width = 24,
height = 24,
color = "#10161F",
}: PdfCuteReIconProps) => {
return (
<Svg width={width} height={height} fill="none" viewBox="0 0 24 24">
<Path
stroke={color}
strokeLinejoin="round"
strokeWidth={2}
d="M10.5 2.5a3 3 0 0 1 3 3v.6c0 .372 0 .557.025.713a2 2 0 0 0 1.662 1.662c.156.025.341.025.713.025h.6a3 3 0 0 1 3 3M12 12l-.074.468a6 6 0 0 1-2.156 3.734l-.368.298.442-.17a6 6 0 0 1 4.31 0l.444.17-.37-.298a6 6 0 0 1-2.155-3.733zm-1.036-9.5h-.296c-2.022 0-3.032 0-3.82.357a4 4 0 0 0-1.991 1.991C4.5 5.636 4.5 6.646 4.5 8.668V14c0 3.288 0 4.931.908 6.038a4 4 0 0 0 .554.554c1.107.908 2.75.908 6.038.908 3.287 0 4.931 0 6.038-.908.202-.166.388-.352.554-.554.908-1.107.908-2.75.908-6.038v-2.964c0-1.033 0-1.55-.082-2.042a6 6 0 0 0-1.033-2.492C18.095 6.095 17.73 5.73 17 5s-1.095-1.095-1.502-1.385a6 6 0 0 0-2.492-1.033c-.492-.082-1.009-.082-2.042-.082Z"
/>
</Svg>
)
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path stroke="#10161F" stroke-linejoin="round" stroke-width="2" d="M10.5 2.5a3 3 0 0 1 3 3v.6c0 .372 0 .557.025.713a2 2 0 0 0 1.662 1.662c.156.025.341.025.713.025h.6a3 3 0 0 1 3 3M12 12l-.074.468a6 6 0 0 1-2.156 3.734l-.368.298.442-.17a6 6 0 0 1 4.31 0l.444.17-.37-.298a6 6 0 0 1-2.155-3.733zm-1.036-9.5h-.296c-2.022 0-3.032 0-3.82.357a4 4 0 0 0-1.991 1.991C4.5 5.636 4.5 6.646 4.5 8.668V14c0 3.288 0 4.931.908 6.038a4 4 0 0 0 .554.554c1.107.908 2.75.908 6.038.908 3.287 0 4.931 0 6.038-.908.202-.166.388-.352.554-.554.908-1.107.908-2.75.908-6.038v-2.964c0-1.033 0-1.55-.082-2.042a6 6 0 0 0-1.033-2.492C18.095 6.095 17.73 5.73 17 5s-1.095-1.095-1.502-1.385a6 6 0 0 0-2.492-1.033c-.492-.082-1.009-.082-2.042-.082Z"/></svg>

After

Width:  |  Height:  |  Size: 852 B