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:
@@ -208,6 +208,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||
this.saveAsPdfHandler = () => this.handleSaveAsPdf()
|
||||
this.printHandler = () => this.handlePrint()
|
||||
|
||||
this.linkClickHandler = this.handleLinkClick.bind(this)
|
||||
@@ -297,58 +298,77 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.exportAsDocument('md')
|
||||
}
|
||||
|
||||
handleSaveAsHtml () {
|
||||
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
||||
const {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
codeBlockTheme,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
} = this.getStyleParams()
|
||||
htmlContentFormatter (noteContent, exportTasks, targetDir) {
|
||||
const {
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
codeBlockTheme,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
} = this.getStyleParams()
|
||||
|
||||
const inlineStyles = buildStyle(
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
)
|
||||
let body = this.markdown.render(noteContent)
|
||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||
files.forEach(file => {
|
||||
if (global.process.platform === 'win32') {
|
||||
file = file.replace('file:///', '')
|
||||
} else {
|
||||
file = file.replace('file://', '')
|
||||
}
|
||||
exportTasks.push({
|
||||
src: file,
|
||||
dst: 'css'
|
||||
const inlineStyles = buildStyle(
|
||||
fontFamily,
|
||||
fontSize,
|
||||
codeBlockFontFamily,
|
||||
lineNumber,
|
||||
scrollPastEnd,
|
||||
theme,
|
||||
allowCustomCSS,
|
||||
customCSS
|
||||
)
|
||||
let body = this.markdown.render(noteContent)
|
||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||
files.forEach(file => {
|
||||
if (global.process.platform === 'win32') {
|
||||
file = file.replace('file:///', '')
|
||||
} else {
|
||||
file = file.replace('file://', '')
|
||||
}
|
||||
exportTasks.push({
|
||||
src: file,
|
||||
dst: 'css'
|
||||
})
|
||||
})
|
||||
|
||||
let styles = ''
|
||||
files.forEach(file => {
|
||||
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
||||
})
|
||||
|
||||
return `<html>
|
||||
<head>
|
||||
<base href="file://${targetDir}/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||
<style id="style">${inlineStyles}</style>
|
||||
${styles}
|
||||
</head>
|
||||
<body>${body}</body>
|
||||
</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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
let styles = ''
|
||||
files.forEach(file => {
|
||||
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
||||
})
|
||||
|
||||
return `<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||
<style id="style">${inlineStyles}</style>
|
||||
${styles}
|
||||
</head>
|
||||
<body>${body}</body>
|
||||
</html>`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -490,6 +510,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||
eventEmitter.on('export:save-pdf', this.saveAsPdfHandler)
|
||||
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-md', this.saveAsMdHandler)
|
||||
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||
eventEmitter.off('export:save-pdf', this.saveAsPdfHandler)
|
||||
eventEmitter.off('print', this.printHandler)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class InfoPanel extends React.Component {
|
||||
|
||||
render () {
|
||||
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
|
||||
return (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
@@ -85,6 +85,11 @@ class InfoPanel extends React.Component {
|
||||
<p>{i18n.__('.html')}</p>
|
||||
</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')}>
|
||||
<i className='fa fa-print' />
|
||||
<p>{i18n.__('Print')}</p>
|
||||
@@ -104,6 +109,7 @@ InfoPanel.propTypes = {
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired,
|
||||
exportAsPdf: PropTypes.func.isRequired,
|
||||
wordCount: PropTypes.number,
|
||||
letterCount: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
right 25px
|
||||
position absolute
|
||||
padding 20px 25px 0 25px
|
||||
width 300px
|
||||
// width 300px
|
||||
overflow auto
|
||||
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)
|
||||
|
||||
@@ -5,7 +5,7 @@ import styles from './InfoPanel.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
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>
|
||||
@@ -46,7 +46,7 @@ const InfoPanelTrashed = ({
|
||||
<p>.html</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--unable'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>.pdf</p>
|
||||
</button>
|
||||
@@ -61,7 +61,8 @@ InfoPanelTrashed.propTypes = {
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired
|
||||
exportAsHtml: PropTypes.func.isRequired,
|
||||
exportAsPdf: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanelTrashed, styles)
|
||||
|
||||
@@ -201,6 +201,10 @@ class MarkdownNoteDetail extends React.Component {
|
||||
ee.emit('export:save-html')
|
||||
}
|
||||
|
||||
exportAsPdf () {
|
||||
ee.emit('export:save-pdf')
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
switch (e.keyCode) {
|
||||
// tab key
|
||||
@@ -426,6 +430,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -492,6 +497,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
|
||||
@@ -657,6 +657,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
'export-txt': 'Text export',
|
||||
'export-md': 'Markdown export',
|
||||
'export-html': 'HTML export',
|
||||
'export-pdf': 'PDF export',
|
||||
'print': 'Print'
|
||||
})[msg]
|
||||
|
||||
@@ -770,6 +771,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
exportAsHtml={this.showWarning}
|
||||
exportAsPdf={this.showWarning}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -818,6 +820,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
exportAsHtml={this.showWarning}
|
||||
exportAsPdf={this.showWarning}
|
||||
type={note.type}
|
||||
print={this.showWarning}
|
||||
/>
|
||||
|
||||
@@ -496,6 +496,7 @@ class NoteList extends React.Component {
|
||||
'export-txt': 'Text export',
|
||||
'export-md': 'Markdown export',
|
||||
'export-html': 'HTML export',
|
||||
'export-pdf': 'PDF export',
|
||||
'print': 'Print'
|
||||
})[msg]
|
||||
|
||||
|
||||
@@ -43,19 +43,18 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
|
||||
.then(function exportNotes (data) {
|
||||
const { storage, notes } = data
|
||||
|
||||
notes
|
||||
return Promise.all(notes
|
||||
.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}`)
|
||||
exportNote(note.key, storage.path, note.content, notePath, null)
|
||||
return exportNote(note.key, storage.path, note.content, notePath, null)
|
||||
})
|
||||
|
||||
return {
|
||||
).then(() => ({
|
||||
storage,
|
||||
folderKey,
|
||||
fileType,
|
||||
exportDir
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -43,14 +43,17 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
|
||||
.then(() => {
|
||||
return saveToFile(exportedData, targetPath)
|
||||
.then(() => exportedData)
|
||||
.then(data => {
|
||||
return saveToFile(data, targetPath)
|
||||
}).catch((err) => {
|
||||
rollbackExport(tasks)
|
||||
throw err
|
||||
|
||||
@@ -141,6 +141,13 @@ const file = {
|
||||
mainWindow.webContents.send('list:isMarkdownNote', 'export-html')
|
||||
mainWindow.webContents.send('export:save-html')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'PDF (.pdf)',
|
||||
click () {
|
||||
mainWindow.webContents.send('list:isMarkdownNote', 'export-pdf')
|
||||
mainWindow.webContents.send('export:save-pdf')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Print",
|
||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
||||
"Storage Locations": "Storage Locations",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Drucken",
|
||||
"Your preferences for Boostnote": "Boostnote Einstellungen",
|
||||
"Storage Locations": "Speicherverwaltung",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Print",
|
||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
||||
"Help": "Help",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Imprimir",
|
||||
"Your preferences for Boostnote": "Tus preferencias para Boostnote",
|
||||
"Storage Locations": "Almacenamientos",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "پرینت",
|
||||
"Your preferences for Boostnote": "تنظیمات شما برای boostnote",
|
||||
"Storage Locations": "ذخیره سازی",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Imprimer",
|
||||
"Your preferences for Boostnote": "Vos préférences pour Boostnote",
|
||||
"Storage Locations": "Stockages",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Nyomtatás",
|
||||
"Your preferences for Boostnote": "Boostnote beállításaid",
|
||||
"Help": "Súgó",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Stampa",
|
||||
"Your preferences for Boostnote": "Le tue preferenze per Boostnote",
|
||||
"Storage Locations": "Posizioni",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "印刷",
|
||||
"Your preferences for Boostnote": "Boostnoteの個人設定",
|
||||
"Help": "ヘルプ",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "인쇄",
|
||||
"Your preferences for Boostnote": "Boostnote 설정",
|
||||
"Storage Locations": "저장소",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Print",
|
||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
||||
"Storage Locations": "Storage Locations",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Drukuj",
|
||||
"Help": "Pomoc",
|
||||
"Your preferences for Boostnote": "Twoje ustawienia dla Boostnote",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Imprimir",
|
||||
"Your preferences for Boostnote": "Suas preferências para o Boostnote",
|
||||
"Storage Locations": "Armazenamentos",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Imprimir",
|
||||
"Your preferences for Boostnote": "As tuas definiçōes para Boostnote",
|
||||
"Storage Locations": "Locais de Armazenamento",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Print",
|
||||
"Your preferences for Boostnote": "Настройки Boostnote",
|
||||
"Storage Locations": "Хранилища",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Print",
|
||||
"Your preferences for Boostnote": "Your preferences for Boostnote",
|
||||
"Storage Locations": "Storage Locations",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "พิมพ์",
|
||||
"Your preferences for Boostnote": "การตั้งค่าของคุณสำหรับ Boostnote",
|
||||
"Help": "ช่วยเหลือ",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "Yazdır",
|
||||
"Your preferences for Boostnote": "Boostnote tercihleriniz",
|
||||
"Storage Locations": "Saklama Alanları",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "打印",
|
||||
"Your preferences for Boostnote": "个性设置",
|
||||
"Storage Locations": "本地存储",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
".md": ".md",
|
||||
".txt": ".txt",
|
||||
".html": ".html",
|
||||
".pdf": ".pdf",
|
||||
"Print": "列印",
|
||||
"Your preferences for Boostnote": "Boostnote 偏好設定",
|
||||
"Storage Locations": "儲存空間",
|
||||
|
||||
Reference in New Issue
Block a user