Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pandoc export #1273

Merged
merged 6 commits into from Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/note/index.js
Expand Up @@ -7,7 +7,7 @@ const { Note, User } = require('../models')

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

async function getNoteById (noteId, { includeUser } = { includeUser: false }) {
const id = await Note.parseNoteIdAsync(noteId)
Expand Down Expand Up @@ -161,6 +161,9 @@ async function noteActions (req, res) {
case 'revision':
actionRevision(req, res, note)
break
case 'pandoc':
actionPandoc(req, res, note)
break
default:
return res.redirect(config.serverURL + '/' + noteId)
}
Expand Down
59 changes: 59 additions & 0 deletions lib/note/noteActions.js
Expand Up @@ -6,6 +6,7 @@ const markdownpdf = require('markdown-pdf')
const shortId = require('shortid')
const querystring = require('querystring')
const moment = require('moment')
const { Pandoc } = require('@hackmd/pandoc.js')

const config = require('../config')
const logger = require('../logger')
Expand Down Expand Up @@ -99,6 +100,63 @@ function actionPDF (req, res, note) {
})
}

const outputFormats = {
asciidoc: 'text/plain',
context: 'application/x-latex',
epub: 'application/epub+zip',
epub3: 'application/epub+zip',
latex: 'application/x-latex',
odt: 'application/vnd.oasis.opendocument.text',
pdf: 'application/pdf',
rst: 'text/plain',
rtf: 'application/rtf',
textile: 'text/plain',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}

async function actionPandoc (req, res, note) {
var url = config.serverURL || 'http://' + req.get('host')
var body = note.content
var extracted = Note.extractMeta(body)
var content = extracted.markdown
var title = Note.decodeTitle(note.title)

if (!fs.existsSync(config.tmpPath)) {
fs.mkdirSync(config.tmpPath)
}
const pandoc = new Pandoc()

var path = config.tmpPath + '/' + Date.now()
content = content.replace(/\]\(\//g, '](' + url + '/')

// TODO: check export type
const { exportType } = req.query

try {
// TODO: timeout rejection

await pandoc.convertToFile(content, 'markdown', exportType, path, [
'--metadata', `title=${title}`
])

var stream = fs.createReadStream(path)
var filename = title
// Be careful of special characters
filename = encodeURIComponent(filename)
// Ideally this should strip them
res.setHeader('Content-disposition', `attachment; filename="${filename}.${exportType}"`)
res.setHeader('Cache-Control', 'private')
res.setHeader('Content-Type', `${outputFormats[exportType]}; charset=UTF-8`)
res.setHeader('X-Robots-Tag', 'noindex, nofollow') // prevent crawling
stream.pipe(res)
} catch (err) {
// TODO: handle error
res.json({
message: err.message
})
}
}

function actionGist (req, res, note) {
const data = {
client_id: config.github.clientID,
Expand Down Expand Up @@ -161,4 +219,5 @@ exports.actionDownload = actionDownload
exports.actionInfo = actionInfo
exports.actionPDF = actionPDF
exports.actionGist = actionGist
exports.actionPandoc = actionPandoc
exports.actionRevision = actionRevision
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -37,6 +37,7 @@
"@hackmd/js-sequence-diagrams": "~0.0.1-alpha.3",
"@hackmd/lz-string": "~1.4.4",
"@hackmd/meta-marked": "~0.4.4",
"@hackmd/pandoc.js": "^0.1.9",
"@passport-next/passport-openid": "~1.0.0",
"@susisu/mte-kernel": "^2.1.0",
"archiver": "~3.1.1",
Expand Down Expand Up @@ -107,10 +108,10 @@
"mysql": "~2.17.1",
"mysql2": "^2.0.1",
"passport": "~0.4.0",
"passport-bitbucket-oauth2": "~0.1.2",
"passport-dropbox-oauth2": "~1.1.0",
"passport-facebook": "~2.1.1",
"passport-github": "~1.1.0",
"passport-bitbucket-oauth2": "~0.1.2",
"passport-gitlab2": "~4.0.0",
"passport-google-oauth20": "~1.0.0",
"passport-ldapauth": "~2.1.3",
Expand Down
9 changes: 9 additions & 0 deletions public/js/index.js
Expand Up @@ -950,6 +950,15 @@ ui.toolbar.download.rawhtml.click(function (e) {
})
// pdf
ui.toolbar.download.pdf.attr('download', '').attr('href', noteurl + '/pdf')

ui.modal.pandocExport.find('#pandoc-export-download').click(function (e) {
e.preventDefault()

const exportType = ui.modal.pandocExport.find('select[name="output"]').val()

window.open(`${noteurl}/pandoc?exportType=${exportType}`, '_blank')
})

// export to dropbox
ui.toolbar.export.dropbox.click(function () {
var filename = renderFilename(ui.area.markdown) + '.md'
Expand Down
3 changes: 2 additions & 1 deletion public/js/lib/editor/ui-elements.js
Expand Up @@ -79,7 +79,8 @@ export const getUIElements = () => ({
modal: {
snippetImportProjects: $('#snippetImportModalProjects'),
snippetImportSnippets: $('#snippetImportModalSnippets'),
revision: $('#revisionModal')
revision: $('#revisionModal'),
pandocExport: $('.pandoc-export-modal')
}
})

Expand Down
1 change: 1 addition & 0 deletions public/views/codimd/body.ejs
Expand Up @@ -250,3 +250,4 @@
<%- include ../shared/signin-modal %>
<%- include ../shared/help-modal %>
<%- include ../shared/revision-modal %>
<%- include ../shared/pandoc-export-modal %>
4 changes: 4 additions & 0 deletions public/views/codimd/header.ejs
Expand Up @@ -70,6 +70,8 @@
<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>
</li>
<% } %>
<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>
</li>
<li class="divider"></li>
<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>
</li>
Expand Down Expand Up @@ -172,6 +174,8 @@
<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>
</li>
<% } %>
<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>
</li>
</ul>
</li>
</ul>
Expand Down
32 changes: 32 additions & 0 deletions public/views/shared/pandoc-export-modal.ejs
@@ -0,0 +1,32 @@
<!-- pandoc export modal -->
<div class="modal fade pandoc-export-modal" id="pandoc-export-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel"><%= __('Export with pandoc') %></h4>
</div>
<form action="#" class="form-inline">
<div class="modal-body">
<strong><%= __('Select output format') %></strong>
<select name="output" id="output" class="form-control">
<option value="asciidoc">AsciiDoc</option>
<option value="context">ConTeXt</option>
<option value="epub">EPUB (.epub)</option>
<option value="epub3">EPUB v3 (.epub3)</option>
<option value="latex">LaTeX</option>
<option value="odt">OpenOffice (.odt)</option>
<option value="rst">reStructuredText (.rst)</option>
<option value="rtf">Rich Text Format (.rtf)</option>
<option value="textile">Textile</option>
<option value="docx">Word (.docx)</option>
</select>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" id="pandoc-export-download"><%= __('Export') %></button>
</div>
</form>
</div>
</div>
</div>