| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const sql = require('../../services/sql'); | 
					
						
							| 
									
										
										
										
											2017-12-03 09:19:48 -05:00
										 |  |  | const html = require('html'); | 
					
						
							| 
									
										
										
										
											2018-02-25 10:55:21 -05:00
										 |  |  | const tar = require('tar-stream'); | 
					
						
							|  |  |  | const sanitize = require("sanitize-filename"); | 
					
						
							| 
									
										
										
										
											2018-03-31 09:07:58 -04:00
										 |  |  | const repository = require("../../services/repository"); | 
					
						
							| 
									
										
										
										
											2018-05-27 12:26:34 -04:00
										 |  |  | const utils = require('../../services/utils'); | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 15:34:07 -04:00
										 |  |  | async function exportNote(req, res) { | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  |     const noteId = req.params.noteId; | 
					
						
							| 
									
										
										
										
											2018-05-27 12:26:34 -04:00
										 |  |  |     const format = req.params.format; | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-24 21:39:15 -04:00
										 |  |  |     const branchId = await sql.getValue('SELECT branchId FROM branches WHERE noteId = ?', [noteId]); | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-27 12:26:34 -04:00
										 |  |  |     if (format === 'tar') { | 
					
						
							|  |  |  |         await exportToTar(branchId, res); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else if (format === 'opml') { | 
					
						
							|  |  |  |         await exportToOpml(branchId, res); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else { | 
					
						
							|  |  |  |         return [404, "Unrecognized export format " + format]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function escapeXmlAttribute(text) { | 
					
						
							|  |  |  |     return text.replace(/&/g, '&') | 
					
						
							|  |  |  |         .replace(/</g, '<') | 
					
						
							|  |  |  |         .replace(/>/g, '>') | 
					
						
							|  |  |  |         .replace(/"/g, '"') | 
					
						
							|  |  |  |         .replace(/'/g, '''); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function prepareText(text) { | 
					
						
							|  |  |  |     const newLines = text.replace(/(<p[^>]*>|<br\s*\/?>)/g, '\n') | 
					
						
							|  |  |  |                          .replace(/ /g, ' '); // nbsp isn't in XML standard (only HTML)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const stripped = utils.stripTags(newLines); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const escaped = escapeXmlAttribute(stripped); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return escaped.replace(/\n/g, '
'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function exportToOpml(branchId, res) { | 
					
						
							|  |  |  |     const branch = await repository.getBranch(branchId); | 
					
						
							|  |  |  |     const note = await branch.getNote(); | 
					
						
							|  |  |  |     const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title; | 
					
						
							|  |  |  |     const sanitizedTitle = sanitize(title); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async function exportNoteInner(branchId) { | 
					
						
							|  |  |  |         const branch = await repository.getBranch(branchId); | 
					
						
							|  |  |  |         const note = await branch.getNote(); | 
					
						
							|  |  |  |         const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const preparedTitle = prepareText(title); | 
					
						
							|  |  |  |         const preparedContent = prepareText(note.content); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const child of await note.getChildBranches()) { | 
					
						
							|  |  |  |             await exportNoteInner(child.branchId); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         res.write('</outline>'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     res.setHeader('Content-Disposition', 'file; filename="' + sanitizedTitle + '.opml"'); | 
					
						
							|  |  |  |     res.setHeader('Content-Type', 'text/x-opml'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     res.write(`<?xml version="1.0" encoding="UTF-8"?>
 | 
					
						
							|  |  |  | <opml version="1.0"> | 
					
						
							|  |  |  | <head> | 
					
						
							|  |  |  | <title>Trilium export</title> | 
					
						
							|  |  |  | </head> | 
					
						
							|  |  |  | <body>`);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await exportNoteInner(branchId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     res.write(`</body>
 | 
					
						
							|  |  |  | </opml>`); | 
					
						
							|  |  |  |     res.end(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function exportToTar(branchId, res) { | 
					
						
							| 
									
										
										
										
											2018-02-25 10:55:21 -05:00
										 |  |  |     const pack = tar.pack(); | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |     const exportedNoteIds = []; | 
					
						
							|  |  |  |     const name = await exportNoteInner(branchId, ''); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async function exportNoteInner(branchId, directory) { | 
					
						
							|  |  |  |         const branch = await repository.getBranch(branchId); | 
					
						
							|  |  |  |         const note = await branch.getNote(); | 
					
						
							|  |  |  |         const childFileName = directory + sanitize(note.title); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (exportedNoteIds.includes(note.noteId)) { | 
					
						
							|  |  |  |             saveMetadataFile(childFileName, { | 
					
						
							|  |  |  |                 version: 1, | 
					
						
							|  |  |  |                 clone: true, | 
					
						
							|  |  |  |                 noteId: note.noteId, | 
					
						
							|  |  |  |                 prefix: branch.prefix | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const metadata = { | 
					
						
							|  |  |  |             version: 1, | 
					
						
							|  |  |  |             clone: false, | 
					
						
							|  |  |  |             noteId: note.noteId, | 
					
						
							|  |  |  |             title: note.title, | 
					
						
							|  |  |  |             prefix: branch.prefix, | 
					
						
							|  |  |  |             type: note.type, | 
					
						
							|  |  |  |             mime: note.mime, | 
					
						
							| 
									
										
										
										
											2018-08-07 13:33:10 +02:00
										 |  |  |             attributes: (await note.getOwnedAttributes()).map(attribute => { | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |                 return { | 
					
						
							| 
									
										
										
										
											2018-08-07 13:33:10 +02:00
										 |  |  |                     type: attribute.type, | 
					
						
							|  |  |  |                     name: attribute.name, | 
					
						
							|  |  |  |                     value: attribute.value, | 
					
						
							|  |  |  |                     isInheritable: attribute.isInheritable, | 
					
						
							|  |  |  |                     position: attribute.position | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |                 }; | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-07 13:33:10 +02:00
										 |  |  |         if (metadata.attributes.find(attributes => attributes.type === 'label' && attributes.name === 'excludeFromExport')) { | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         saveMetadataFile(childFileName, metadata); | 
					
						
							|  |  |  |         saveDataFile(childFileName, note); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         exportedNoteIds.push(note.noteId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const child of await note.getChildBranches()) { | 
					
						
							|  |  |  |             await exportNoteInner(child.branchId, childFileName + "/"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return childFileName; | 
					
						
							| 
									
										
										
										
											2018-02-26 00:07:43 -05:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |     function saveDataFile(childFileName, note) { | 
					
						
							|  |  |  |         const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content; | 
					
						
							| 
									
										
										
										
											2018-02-26 20:47:34 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |         pack.entry({name: childFileName + ".dat", size: content.length}, content); | 
					
						
							| 
									
										
										
										
											2018-02-26 20:47:34 -05:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |     function saveMetadataFile(childFileName, metadata) { | 
					
						
							|  |  |  |         const metadataJson = JSON.stringify(metadata, null, '\t'); | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |         pack.entry({name: childFileName + ".meta", size: metadataJson.length}, metadataJson); | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-02-25 10:55:21 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |     pack.finalize(); | 
					
						
							| 
									
										
										
										
											2018-02-25 10:55:21 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-08 13:14:30 -04:00
										 |  |  |     res.setHeader('Content-Disposition', 'file; filename="' + name + '.tar"'); | 
					
						
							|  |  |  |     res.setHeader('Content-Type', 'application/tar'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pack.pipe(res); | 
					
						
							| 
									
										
										
										
											2017-12-02 21:48:22 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 15:34:07 -04:00
										 |  |  | module.exports = { | 
					
						
							|  |  |  |     exportNote | 
					
						
							|  |  |  | }; |