1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

Merge pull request #2678 from AgentEpsilon/export-pdf

Export a Markdown note as PDF
This commit is contained in:
Junyoung Choi
2019-03-21 01:20:12 +09:00
committed by GitHub
30 changed files with 131 additions and 63 deletions

View File

@@ -208,6 +208,7 @@ export default class MarkdownPreview extends React.Component {
this.saveAsTextHandler = () => this.handleSaveAsText() this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd() this.saveAsMdHandler = () => this.handleSaveAsMd()
this.saveAsHtmlHandler = () => this.handleSaveAsHtml() this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.saveAsPdfHandler = () => this.handleSaveAsPdf()
this.printHandler = () => this.handlePrint() this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handleLinkClick.bind(this) this.linkClickHandler = this.handleLinkClick.bind(this)
@@ -297,8 +298,7 @@ export default class MarkdownPreview extends React.Component {
this.exportAsDocument('md') this.exportAsDocument('md')
} }
handleSaveAsHtml () { htmlContentFormatter (noteContent, exportTasks, targetDir) {
this.exportAsDocument('html', (noteContent, exportTasks) => {
const { const {
fontFamily, fontFamily,
fontSize, fontSize,
@@ -342,6 +342,7 @@ export default class MarkdownPreview extends React.Component {
return `<html> return `<html>
<head> <head>
<base href="file://${targetDir}/">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1"> <meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
<style id="style">${inlineStyles}</style> <style id="style">${inlineStyles}</style>
@@ -349,6 +350,25 @@ export default class MarkdownPreview extends React.Component {
</head> </head>
<body>${body}</body> <body>${body}</body>
</html>` </html>`
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
}
handleSaveAsPdf () {
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false}})
printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
return new Promise((resolve, reject) => {
printout.webContents.on('did-finish-load', () => {
printout.webContents.printToPDF({}, (err, data) => {
if (err) reject(err)
else resolve(data)
printout.destroy()
})
})
})
}) })
} }
@@ -490,6 +510,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.on('export:save-text', this.saveAsTextHandler) eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler) eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler) eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
eventEmitter.on('export:save-pdf', this.saveAsPdfHandler)
eventEmitter.on('print', this.printHandler) eventEmitter.on('print', this.printHandler)
} }
@@ -527,6 +548,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.off('export:save-text', this.saveAsTextHandler) eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler) eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler) eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
eventEmitter.off('export:save-pdf', this.saveAsPdfHandler)
eventEmitter.off('print', this.printHandler) eventEmitter.off('print', this.printHandler)
} }

View File

@@ -14,7 +14,7 @@ class InfoPanel extends React.Component {
render () { render () {
const { const {
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
} = this.props } = this.props
return ( return (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
@@ -85,6 +85,11 @@ class InfoPanel extends React.Component {
<p>{i18n.__('.html')}</p> <p>{i18n.__('.html')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<i className='fa fa-file-pdf-o' />
<p>{i18n.__('.pdf')}</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e, 'print')}> <button styleName='export--enable' onClick={(e) => print(e, 'print')}>
<i className='fa fa-print' /> <i className='fa fa-print' />
<p>{i18n.__('Print')}</p> <p>{i18n.__('Print')}</p>
@@ -104,6 +109,7 @@ InfoPanel.propTypes = {
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired, exportAsHtml: PropTypes.func.isRequired,
exportAsPdf: PropTypes.func.isRequired,
wordCount: PropTypes.number, wordCount: PropTypes.number,
letterCount: PropTypes.number, letterCount: PropTypes.number,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,

View File

@@ -15,7 +15,7 @@
right 25px right 25px
position absolute position absolute
padding 20px 25px 0 25px padding 20px 25px 0 25px
width 300px // width 300px
overflow auto overflow auto
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1) box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)

View File

@@ -5,7 +5,7 @@ import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({ const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
}) => ( }) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div> <div>
@@ -46,7 +46,7 @@ const InfoPanelTrashed = ({
<p>.html</p> <p>.html</p>
</button> </button>
<button styleName='export--unable'> <button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<i className='fa fa-file-pdf-o' /> <i className='fa fa-file-pdf-o' />
<p>.pdf</p> <p>.pdf</p>
</button> </button>
@@ -61,7 +61,8 @@ InfoPanelTrashed.propTypes = {
createdAt: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired exportAsHtml: PropTypes.func.isRequired,
exportAsPdf: PropTypes.func.isRequired
} }
export default CSSModules(InfoPanelTrashed, styles) export default CSSModules(InfoPanelTrashed, styles)

View File

@@ -201,6 +201,10 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('export:save-html') ee.emit('export:save-html')
} }
exportAsPdf () {
ee.emit('export:save-pdf')
}
handleKeyDown (e) { handleKeyDown (e) {
switch (e.keyCode) { switch (e.keyCode) {
// tab key // tab key
@@ -426,6 +430,7 @@ class MarkdownNoteDetail extends React.Component {
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsPdf={this.exportAsPdf}
/> />
</div> </div>
</div> </div>
@@ -492,6 +497,7 @@ class MarkdownNoteDetail extends React.Component {
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
exportAsPdf={this.exportAsPdf}
wordCount={note.content.split(' ').length} wordCount={note.content.split(' ').length}
letterCount={note.content.replace(/\r?\n/g, '').length} letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type} type={note.type}

View File

@@ -657,6 +657,7 @@ class SnippetNoteDetail extends React.Component {
'export-txt': 'Text export', 'export-txt': 'Text export',
'export-md': 'Markdown export', 'export-md': 'Markdown export',
'export-html': 'HTML export', 'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print' 'print': 'Print'
})[msg] })[msg]
@@ -770,6 +771,7 @@ class SnippetNoteDetail extends React.Component {
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning} exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
/> />
</div> </div>
</div> </div>
@@ -818,6 +820,7 @@ class SnippetNoteDetail extends React.Component {
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning} exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
type={note.type} type={note.type}
print={this.showWarning} print={this.showWarning}
/> />

View File

@@ -496,6 +496,7 @@ class NoteList extends React.Component {
'export-txt': 'Text export', 'export-txt': 'Text export',
'export-md': 'Markdown export', 'export-md': 'Markdown export',
'export-html': 'HTML export', 'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print' 'print': 'Print'
})[msg] })[msg]

View File

@@ -43,19 +43,18 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
.then(function exportNotes (data) { .then(function exportNotes (data) {
const { storage, notes } = data const { storage, notes } = data
notes return Promise.all(notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE') .filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(note => { .map(note => {
const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`) const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
exportNote(note.key, storage.path, note.content, notePath, null) return exportNote(note.key, storage.path, note.content, notePath, null)
}) })
).then(() => ({
return {
storage, storage,
folderKey, folderKey,
fileType, fileType,
exportDir exportDir
} }))
}) })
} }

View File

@@ -43,14 +43,17 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
) )
if (outputFormatter) { if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks) exportedData = outputFormatter(exportedData, exportTasks, path.dirname(targetPath))
} else {
exportedData = Promise.resolve(exportedData)
} }
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath)) const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst))) return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
.then(() => { .then(() => exportedData)
return saveToFile(exportedData, targetPath) .then(data => {
return saveToFile(data, targetPath)
}).catch((err) => { }).catch((err) => {
rollbackExport(tasks) rollbackExport(tasks)
throw err throw err

View File

@@ -141,6 +141,13 @@ const file = {
mainWindow.webContents.send('list:isMarkdownNote', 'export-html') mainWindow.webContents.send('list:isMarkdownNote', 'export-html')
mainWindow.webContents.send('export:save-html') mainWindow.webContents.send('export:save-html')
} }
},
{
label: 'PDF (.pdf)',
click () {
mainWindow.webContents.send('list:isMarkdownNote', 'export-pdf')
mainWindow.webContents.send('export:save-pdf')
}
} }
] ]
}, },

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations", "Storage Locations": "Storage Locations",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Drucken", "Print": "Drucken",
"Your preferences for Boostnote": "Boostnote Einstellungen", "Your preferences for Boostnote": "Boostnote Einstellungen",
"Storage Locations": "Speicherverwaltung", "Storage Locations": "Speicherverwaltung",

View File

@@ -19,6 +19,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Help": "Help", "Help": "Help",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir", "Print": "Imprimir",
"Your preferences for Boostnote": "Tus preferencias para Boostnote", "Your preferences for Boostnote": "Tus preferencias para Boostnote",
"Storage Locations": "Almacenamientos", "Storage Locations": "Almacenamientos",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "پرینت", "Print": "پرینت",
"Your preferences for Boostnote": "تنظیمات شما برای boostnote", "Your preferences for Boostnote": "تنظیمات شما برای boostnote",
"Storage Locations": "ذخیره سازی", "Storage Locations": "ذخیره سازی",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimer", "Print": "Imprimer",
"Your preferences for Boostnote": "Vos préférences pour Boostnote", "Your preferences for Boostnote": "Vos préférences pour Boostnote",
"Storage Locations": "Stockages", "Storage Locations": "Stockages",

View File

@@ -19,6 +19,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Nyomtatás", "Print": "Nyomtatás",
"Your preferences for Boostnote": "Boostnote beállításaid", "Your preferences for Boostnote": "Boostnote beállításaid",
"Help": "Súgó", "Help": "Súgó",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Stampa", "Print": "Stampa",
"Your preferences for Boostnote": "Le tue preferenze per Boostnote", "Your preferences for Boostnote": "Le tue preferenze per Boostnote",
"Storage Locations": "Posizioni", "Storage Locations": "Posizioni",

View File

@@ -22,6 +22,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "印刷", "Print": "印刷",
"Your preferences for Boostnote": "Boostnoteの個人設定", "Your preferences for Boostnote": "Boostnoteの個人設定",
"Help": "ヘルプ", "Help": "ヘルプ",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "인쇄", "Print": "인쇄",
"Your preferences for Boostnote": "Boostnote 설정", "Your preferences for Boostnote": "Boostnote 설정",
"Storage Locations": "저장소", "Storage Locations": "저장소",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations", "Storage Locations": "Storage Locations",

View File

@@ -19,6 +19,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Drukuj", "Print": "Drukuj",
"Help": "Pomoc", "Help": "Pomoc",
"Your preferences for Boostnote": "Twoje ustawienia dla Boostnote", "Your preferences for Boostnote": "Twoje ustawienia dla Boostnote",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir", "Print": "Imprimir",
"Your preferences for Boostnote": "Suas preferências para o Boostnote", "Your preferences for Boostnote": "Suas preferências para o Boostnote",
"Storage Locations": "Armazenamentos", "Storage Locations": "Armazenamentos",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir", "Print": "Imprimir",
"Your preferences for Boostnote": "As tuas definiçōes para Boostnote", "Your preferences for Boostnote": "As tuas definiçōes para Boostnote",
"Storage Locations": "Locais de Armazenamento", "Storage Locations": "Locais de Armazenamento",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Настройки Boostnote", "Your preferences for Boostnote": "Настройки Boostnote",
"Storage Locations": "Хранилища", "Storage Locations": "Хранилища",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations", "Storage Locations": "Storage Locations",

View File

@@ -19,6 +19,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "พิมพ์", "Print": "พิมพ์",
"Your preferences for Boostnote": "การตั้งค่าของคุณสำหรับ Boostnote", "Your preferences for Boostnote": "การตั้งค่าของคุณสำหรับ Boostnote",
"Help": "ช่วยเหลือ", "Help": "ช่วยเหลือ",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Yazdır", "Print": "Yazdır",
"Your preferences for Boostnote": "Boostnote tercihleriniz", "Your preferences for Boostnote": "Boostnote tercihleriniz",
"Storage Locations": "Saklama Alanları", "Storage Locations": "Saklama Alanları",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "打印", "Print": "打印",
"Your preferences for Boostnote": "个性设置", "Your preferences for Boostnote": "个性设置",
"Storage Locations": "本地存储", "Storage Locations": "本地存储",

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "列印", "Print": "列印",
"Your preferences for Boostnote": "Boostnote 偏好設定", "Your preferences for Boostnote": "Boostnote 偏好設定",
"Storage Locations": "儲存空間", "Storage Locations": "儲存空間",