mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
OPML export support (issue #78), import missing for now
This commit is contained in:
parent
cab54a458f
commit
f47ae12019
@ -94,13 +94,15 @@ const contextMenuOptions = {
|
|||||||
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
|
||||||
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
|
||||||
{title: "----"},
|
{title: "----"},
|
||||||
{title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne"},
|
{title: "Export branch", cmd: "exportBranch", uiIcon: " ui-icon-arrowthick-1-ne", children: [
|
||||||
|
{title: "Native Tar", cmd: "exportBranchToTar"},
|
||||||
|
{title: "OPML", cmd: "exportBranchToOpml"}
|
||||||
|
]},
|
||||||
{title: "Import into branch", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"},
|
{title: "Import into branch", cmd: "importBranch", uiIcon: "ui-icon-arrowthick-1-sw"},
|
||||||
{title: "----"},
|
{title: "----"},
|
||||||
{title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"},
|
{title: "Collapse branch <kbd>Alt+-</kbd>", cmd: "collapseBranch", uiIcon: "ui-icon-minus"},
|
||||||
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
|
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
|
||||||
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
|
||||||
|
|
||||||
],
|
],
|
||||||
beforeOpen: async (event, ui) => {
|
beforeOpen: async (event, ui) => {
|
||||||
const node = $.ui.fancytree.getNode(ui.target);
|
const node = $.ui.fancytree.getNode(ui.target);
|
||||||
@ -163,8 +165,11 @@ const contextMenuOptions = {
|
|||||||
else if (ui.cmd === "delete") {
|
else if (ui.cmd === "delete") {
|
||||||
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "exportBranch") {
|
else if (ui.cmd === "exportBranchToTar") {
|
||||||
exportService.exportBranch(node.data.noteId);
|
exportService.exportBranch(node.data.noteId, 'tar');
|
||||||
|
}
|
||||||
|
else if (ui.cmd === "exportBranchToOpml") {
|
||||||
|
exportService.exportBranch(node.data.noteId, 'opml');
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "importBranch") {
|
else if (ui.cmd === "importBranch") {
|
||||||
exportService.importBranch(node.data.noteId);
|
exportService.importBranch(node.data.noteId);
|
||||||
|
@ -3,9 +3,9 @@ import protectedSessionHolder from './protected_session_holder.js';
|
|||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
|
|
||||||
function exportBranch(noteId) {
|
function exportBranch(noteId, format) {
|
||||||
const url = utils.getHost() + "/api/notes/" + noteId + "/export?protectedSessionId="
|
const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format +
|
||||||
+ encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
|
"?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
|
||||||
|
|
||||||
utils.download(url);
|
utils.download(url);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,10 @@ async function expandToNote(notePath, expandOpts) {
|
|||||||
for (const childNoteId of runPath) {
|
for (const childNoteId of runPath) {
|
||||||
const node = getNodesByNoteId(childNoteId).find(node => node.data.parentNoteId === parentNoteId);
|
const node = getNodesByNoteId(childNoteId).find(node => node.data.parentNoteId === parentNoteId);
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
console.log(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (childNoteId === noteId) {
|
if (childNoteId === noteId) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@ -154,6 +158,8 @@ async function getRunPath(notePath) {
|
|||||||
for (const noteId of pathToRoot) {
|
for (const noteId of pathToRoot) {
|
||||||
effectivePath.push(noteId);
|
effectivePath.push(noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
effectivePath.push('root');
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -5,12 +5,85 @@ const html = require('html');
|
|||||||
const tar = require('tar-stream');
|
const tar = require('tar-stream');
|
||||||
const sanitize = require("sanitize-filename");
|
const sanitize = require("sanitize-filename");
|
||||||
const repository = require("../../services/repository");
|
const repository = require("../../services/repository");
|
||||||
|
const utils = require('../../services/utils');
|
||||||
|
|
||||||
async function exportNote(req, res) {
|
async function exportNote(req, res) {
|
||||||
const noteId = req.params.noteId;
|
const noteId = req.params.noteId;
|
||||||
|
const format = req.params.format;
|
||||||
|
|
||||||
const branchId = await sql.getValue('SELECT branchId FROM branches WHERE noteId = ?', [noteId]);
|
const branchId = await sql.getValue('SELECT branchId FROM branches WHERE noteId = ?', [noteId]);
|
||||||
|
|
||||||
|
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) {
|
||||||
const pack = tar.pack();
|
const pack = tar.pack();
|
||||||
|
|
||||||
const exportedNoteIds = [];
|
const exportedNoteIds = [];
|
||||||
|
@ -122,7 +122,7 @@ function register(app) {
|
|||||||
apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent);
|
apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
|
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
|
||||||
|
|
||||||
route(GET, '/api/notes/:noteId/export', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
|
route(GET, '/api/notes/:noteId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
|
||||||
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importTar, apiResultHandler);
|
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importTar, apiResultHandler);
|
||||||
|
|
||||||
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
|
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
|
||||||
|
@ -75,6 +75,10 @@ function toObject(array, fn) {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripTags(text) {
|
||||||
|
return text.replace(/<(?:.|\n)*?>/gm, '');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
randomSecureToken,
|
randomSecureToken,
|
||||||
randomString,
|
randomString,
|
||||||
@ -88,5 +92,6 @@ module.exports = {
|
|||||||
sanitizeSql,
|
sanitizeSql,
|
||||||
stopWatch,
|
stopWatch,
|
||||||
unescapeHtml,
|
unescapeHtml,
|
||||||
toObject
|
toObject,
|
||||||
|
stripTags
|
||||||
};
|
};
|
Loading…
x
Reference in New Issue
Block a user