Skip to content

Commit 6f78c9a

Browse files
authored
Merge pull request #1273 from hackmdio/feature/support-pandoc-export
Pandoc export
2 parents 7969d17 + d573a45 commit 6f78c9a

File tree

9 files changed

+319
-5
lines changed

9 files changed

+319
-5
lines changed

lib/note/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const { Note, User } = require('../models')
77

88
const { newCheckViewPermission, errorForbidden, responseCodiMD, errorNotFound } = require('../response')
99
const { updateHistory } = require('../history')
10-
const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision } = require('./noteActions')
10+
const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision, actionPandoc } = require('./noteActions')
1111

1212
async function getNoteById (noteId, { includeUser } = { includeUser: false }) {
1313
const id = await Note.parseNoteIdAsync(noteId)
@@ -180,6 +180,9 @@ async function noteActions (req, res) {
180180
case 'revision':
181181
actionRevision(req, res, note)
182182
break
183+
case 'pandoc':
184+
actionPandoc(req, res, note)
185+
break
183186
default:
184187
return res.redirect(config.serverURL + '/' + noteId)
185188
}

lib/note/noteActions.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const markdownpdf = require('markdown-pdf')
66
const shortId = require('shortid')
77
const querystring = require('querystring')
88
const moment = require('moment')
9+
const { Pandoc } = require('@hackmd/pandoc.js')
910

1011
const config = require('../config')
1112
const logger = require('../logger')
@@ -99,6 +100,63 @@ function actionPDF (req, res, note) {
99100
})
100101
}
101102

103+
const outputFormats = {
104+
asciidoc: 'text/plain',
105+
context: 'application/x-latex',
106+
epub: 'application/epub+zip',
107+
epub3: 'application/epub+zip',
108+
latex: 'application/x-latex',
109+
odt: 'application/vnd.oasis.opendocument.text',
110+
pdf: 'application/pdf',
111+
rst: 'text/plain',
112+
rtf: 'application/rtf',
113+
textile: 'text/plain',
114+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
115+
}
116+
117+
async function actionPandoc (req, res, note) {
118+
var url = config.serverURL || 'http://' + req.get('host')
119+
var body = note.content
120+
var extracted = Note.extractMeta(body)
121+
var content = extracted.markdown
122+
var title = Note.decodeTitle(note.title)
123+
124+
if (!fs.existsSync(config.tmpPath)) {
125+
fs.mkdirSync(config.tmpPath)
126+
}
127+
const pandoc = new Pandoc()
128+
129+
var path = config.tmpPath + '/' + Date.now()
130+
content = content.replace(/\]\(\//g, '](' + url + '/')
131+
132+
// TODO: check export type
133+
const { exportType } = req.query
134+
135+
try {
136+
// TODO: timeout rejection
137+
138+
await pandoc.convertToFile(content, 'markdown', exportType, path, [
139+
'--metadata', `title=${title}`
140+
])
141+
142+
var stream = fs.createReadStream(path)
143+
var filename = title
144+
// Be careful of special characters
145+
filename = encodeURIComponent(filename)
146+
// Ideally this should strip them
147+
res.setHeader('Content-disposition', `attachment; filename="${filename}.${exportType}"`)
148+
res.setHeader('Cache-Control', 'private')
149+
res.setHeader('Content-Type', `${outputFormats[exportType]}; charset=UTF-8`)
150+
res.setHeader('X-Robots-Tag', 'noindex, nofollow') // prevent crawling
151+
stream.pipe(res)
152+
} catch (err) {
153+
// TODO: handle error
154+
res.json({
155+
message: err.message
156+
})
157+
}
158+
}
159+
102160
function actionGist (req, res, note) {
103161
const data = {
104162
client_id: config.github.clientID,
@@ -161,4 +219,5 @@ exports.actionDownload = actionDownload
161219
exports.actionInfo = actionInfo
162220
exports.actionPDF = actionPDF
163221
exports.actionGist = actionGist
222+
exports.actionPandoc = actionPandoc
164223
exports.actionRevision = actionRevision

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@hackmd/js-sequence-diagrams": "~0.0.1-alpha.3",
3838
"@hackmd/lz-string": "~1.4.4",
3939
"@hackmd/meta-marked": "~0.4.4",
40+
"@hackmd/pandoc.js": "^0.1.9",
4041
"@passport-next/passport-openid": "~1.0.0",
4142
"@susisu/mte-kernel": "^2.1.0",
4243
"archiver": "~3.1.1",

public/js/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,15 @@ ui.toolbar.download.rawhtml.click(function (e) {
951951
})
952952
// pdf
953953
ui.toolbar.download.pdf.attr('download', '').attr('href', noteurl + '/pdf')
954+
955+
ui.modal.pandocExport.find('#pandoc-export-download').click(function (e) {
956+
e.preventDefault()
957+
958+
const exportType = ui.modal.pandocExport.find('select[name="output"]').val()
959+
960+
window.open(`${noteurl}/pandoc?exportType=${exportType}`, '_blank')
961+
})
962+
954963
// export to dropbox
955964
ui.toolbar.export.dropbox.click(function () {
956965
var filename = renderFilename(ui.area.markdown) + '.md'

public/js/lib/editor/ui-elements.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ export const getUIElements = () => ({
7979
modal: {
8080
snippetImportProjects: $('#snippetImportModalProjects'),
8181
snippetImportSnippets: $('#snippetImportModalSnippets'),
82-
revision: $('#revisionModal')
82+
revision: $('#revisionModal'),
83+
pandocExport: $('.pandoc-export-modal')
8384
}
8485
})
8586

public/views/codimd/body.ejs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,4 @@
250250
<%- include ../shared/signin-modal %>
251251
<%- include ../shared/help-modal %>
252252
<%- include ../shared/revision-modal %>
253+
<%- include ../shared/pandoc-export-modal %>

public/views/codimd/header.ejs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
<li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
7171
</li>
7272
<% } %>
73+
<li role="presentation"><a role="menuitem" class="ui-download-pandoc" tabindex="-1" href="#" target="_self" data-toggle="modal" data-target=".pandoc-export-modal"><i class="fa fa-cloud-download fa-fw"></i> Pandoc (Beta)</a>
74+
</li>
7375
<li class="divider"></li>
7476
<li role="presentation"><a role="menuitem" class="ui-help" href="#" data-toggle="modal" data-target=".help-modal"><i class="fa fa-question-circle fa-fw"></i> Help</a>
7577
</li>
@@ -172,6 +174,8 @@
172174
<li role="presentation"><a role="menuitem" class="ui-download-pdf-beta" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> PDF (Beta)</a>
173175
</li>
174176
<% } %>
177+
<li role="presentation"><a role="menuitem" class="ui-download-pandoc" tabindex="-1" href="#" target="_self" data-toggle="modal" data-target=".pandoc-export-modal"><i class="fa fa-cloud-download fa-fw"></i> Pandoc (Beta)</a>
178+
</li>
175179
</ul>
176180
</li>
177181
</ul>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!-- pandoc export modal -->
2+
<div class="modal fade pandoc-export-modal" id="pandoc-export-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
3+
<div class="modal-dialog modal-sm">
4+
<div class="modal-content">
5+
<div class="modal-header">
6+
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
7+
</button>
8+
<h4 class="modal-title" id="myModalLabel"><%= __('Export with pandoc') %></h4>
9+
</div>
10+
<form action="#" class="form-inline">
11+
<div class="modal-body">
12+
<strong><%= __('Select output format') %></strong>
13+
<select name="output" id="output" class="form-control">
14+
<option value="asciidoc">AsciiDoc</option>
15+
<option value="context">ConTeXt</option>
16+
<option value="epub">EPUB (.epub)</option>
17+
<option value="epub3">EPUB v3 (.epub3)</option>
18+
<option value="latex">LaTeX</option>
19+
<option value="odt">OpenOffice (.odt)</option>
20+
<option value="rst">reStructuredText (.rst)</option>
21+
<option value="rtf">Rich Text Format (.rtf)</option>
22+
<option value="textile">Textile</option>
23+
<option value="docx">Word (.docx)</option>
24+
</select>
25+
</div>
26+
<div class="modal-footer">
27+
<button type="submit" class="btn btn-primary" id="pandoc-export-download"><%= __('Export') %></button>
28+
</div>
29+
</form>
30+
</div>
31+
</div>
32+
</div>

0 commit comments

Comments
 (0)