unified export dialog, WIP

This commit is contained in:
azivner 2018-11-24 14:44:56 +01:00
parent 3e351bd8d3
commit ee23bcc783
12 changed files with 203 additions and 103 deletions

View File

@ -0,0 +1,67 @@
import treeService from '../services/tree.js';
import treeUtils from "../services/tree_utils.js";
import exportService from "../services/export.js";
const $dialog = $("#export-dialog");
const $form = $("#export-form");
const $noteTitle = $dialog.find(".note-title");
const $subtreeFormats = $("#export-subtree-formats");
const $singleFormats = $("#export-single-formats");
const $subtreeType = $("#export-type-subtree");
const $singleType = $("#export-type-single");
async function showDialog(defaultType) {
if (defaultType === 'subtree') {
$subtreeType.prop("checked", true).change();
}
else if (defaultType === 'single') {
$singleType.prop("checked", true).change();
}
else {
throw new Error("Unrecognized type " + defaultType);
}
glob.activeDialog = $dialog;
$dialog.modal();
const currentNode = treeService.getCurrentNode();
const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
$form.submit(() => {
const exportType = $dialog.find("input[name='export-type']:checked").val();
const currentNode = treeService.getCurrentNode();
exportService.exportNote(currentNode.data.branchId, exportType);
$dialog.modal('hide');
return false;
});
$('input[name=export-type]').change(function () {
if (this.value === 'subtree') {
if ($("input[name=export-subtree-format]:checked").length === 0) {
$("input[name=export-subtree-format]:first").prop("checked", true);
}
$subtreeFormats.slideDown();
$singleFormats.slideUp();
}
else {
if ($("input[name=export-single-format]:checked").length === 0) {
$("input[name=export-single-format]:first").prop("checked", true);
}
$subtreeFormats.slideUp();
$singleFormats.slideDown();
}
});
export default {
showDialog
};

View File

@ -1,35 +0,0 @@
import treeService from '../services/tree.js';
import server from '../services/server.js';
import treeUtils from "../services/tree_utils.js";
import exportService from "../services/export.js";
const $dialog = $("#export-subtree-dialog");
const $form = $("#export-subtree-form");
const $noteTitle = $dialog.find(".note-title");
async function showDialog() {
glob.activeDialog = $dialog;
$dialog.modal();
const currentNode = treeService.getCurrentNode();
const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
$form.submit(() => {
const exportFormat = $dialog.find("input[name='export-format']:checked").val();
const currentNode = treeService.getCurrentNode();
exportService.exportSubtree(currentNode.data.branchId, exportFormat);
$dialog.modal('hide');
return false;
});
export default {
showDialog
};

View File

@ -7,6 +7,7 @@ import recentChangesDialog from '../dialogs/recent_changes.js';
import optionsDialog from '../dialogs/options.js';
import sqlConsoleDialog from '../dialogs/sql_console.js';
import markdownImportDialog from '../dialogs/markdown_import.js';
import exportDialog from '../dialogs/export.js';
import cloning from './cloning.js';
import contextMenu from './tree_context_menu.js';
@ -103,12 +104,12 @@ if (utils.isElectron()) {
});
}
$("#export-note-to-markdown-button").click(function () {
$("#export-note-button").click(function () {
if ($(this).hasClass("disabled")) {
return;
}
exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single')
exportDialog.showDialog('single');
});
treeService.showTree();

View File

@ -4,7 +4,7 @@ import protectedSessionHolder from './protected_session_holder.js';
import utils from './utils.js';
import server from './server.js';
function exportSubtree(noteId, format) {
function exportNote(noteId, format) {
const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format +
"?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
@ -47,6 +47,6 @@ $("#import-upload").change(async function() {
});
export default {
exportSubtree,
exportNote,
importIntoNote
};

View File

@ -564,8 +564,6 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
clearSelectedNodes(); // to unmark previously active node
infoService.showMessage("Created!");
return {note, branch};
}

View File

@ -6,7 +6,7 @@ import protectedSessionService from './protected_session.js';
import treeChangesService from './branches.js';
import treeUtils from './tree_utils.js';
import branchPrefixDialog from '../dialogs/branch_prefix.js';
import exportSubtreeDialog from '../dialogs/export_subtree.js';
import exportDialog from '../dialogs/export.js';
import infoService from "./info.js";
import treeCache from "./tree_cache.js";
import syncService from "./sync.js";
@ -93,7 +93,7 @@ const contextMenuItems = [
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "clipboard"},
{title: "Paste after", cmd: "pasteAfter", uiIcon: "clipboard"},
{title: "----"},
{title: "Export subtree", cmd: "exportSubtree", uiIcon: "arrow-up-right"},
{title: "Export", cmd: "export", uiIcon: "arrow-up-right"},
{title: "Import into note (tar, opml, md, enex)", cmd: "importIntoNote", uiIcon: "arrow-down-left"},
{title: "----"},
{title: "Collapse subtree <kbd>Alt+-</kbd>", cmd: "collapseSubtree", uiIcon: "align-justify"},
@ -127,7 +127,7 @@ async function getContextMenuItems(event) {
enableItem("pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search');
enableItem("pasteInto", clipboardIds.length > 0 && note.type !== 'search');
enableItem("importIntoNote", note.type !== 'search');
enableItem("exportSubtree", note.type !== 'search');
enableItem("export", note.type !== 'search');
enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search');
// Activate node on right-click
@ -179,8 +179,8 @@ function selectContextMenuItem(event, cmd) {
else if (cmd === "delete") {
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
}
else if (cmd === "exportSubtree") {
exportSubtreeDialog.showDialog();
else if (cmd === "export") {
exportDialog.showDialog("subtree");
}
else if (cmd === "importIntoNote") {
exportService.importIntoNote(node.data.noteId);

View File

@ -684,4 +684,14 @@ div[data-notify="container"] {
font-size: x-large;
color: #777;
z-index: 100;
}
#export-form .form-check {
padding-top: 10px;
padding-bottom: 10px;
}
#export-form .format-choice {
padding-left: 40px;
display: none;
}

View File

@ -9,14 +9,15 @@ const repository = require("../../services/repository");
async function exportNote(req, res) {
// entityId maybe either noteId or branchId depending on format
const entityId = req.params.entityId;
const type = req.params.type;
const format = req.params.format;
if (format === 'native-tar') {
await nativeTarExportService.exportToTar(await repository.getBranch(entityId), res);
}
else if (format === 'markdown-tar') {
await markdownTarExportService.exportToMarkdown(await repository.getBranch(entityId), res);
if (type === 'tar') {
await nativeTarExportService.exportToTar(await repository.getBranch(entityId), format, res);
}
// else if (format === 'tar') {
// await markdownTarExportService.exportToMarkdown(await repository.getBranch(entityId), res);
// }
// export single note without subtree
else if (format === 'markdown-single') {
await markdownSingleExportService.exportSingleMarkdown(await repository.getNote(entityId), res);

View File

@ -3,8 +3,15 @@
const html = require('html');
const native_tar = require('tar-stream');
const sanitize = require("sanitize-filename");
const mimeTypes = require('mime-types');
const TurndownService = require('turndown');
/**
* @param format - 'html' or 'markdown'
*/
async function exportToTar(branch, format, res) {
const turndownService = new TurndownService();
async function exportToTar(branch, res) {
const pack = native_tar.pack();
const exportedNoteIds = [];
@ -52,6 +59,10 @@ async function exportToTar(branch, res) {
})
};
if (note.type === 'text') {
metadata.format = format;
}
if (await note.hasLabel('excludeFromExport')) {
return;
}
@ -75,9 +86,35 @@ async function exportToTar(branch, res) {
}
function saveDataFile(childFileName, note) {
const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content;
let content = note.content;
pack.entry({name: childFileName + ".dat", size: content.length}, content);
if (note.type === 'text') {
if (format === 'html') {
content = html.prettyPrint(note.content, {indent_size: 2});
}
else if (format === 'markdown') {
content = turndownService.turndown(note.content);
}
else {
throw new Error("Unknown format: " + format);
}
}
const extension = mimeTypes.extension(note.mime)
|| getExceptionalExtension(note.mime)
|| "dat";
if (!childFileName.toLowerCase().endsWith(extension)) {
childFileName += "." + extension;
}
pack.entry({name: childFileName, size: content.length}, content);
}
function getExceptionalExtension(mime) {
if (mime === 'application/x-javascript') {
return 'js';
}
}
function saveMetadataFile(childFileName, metadata) {

View File

@ -0,0 +1,67 @@
<div id="export-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Export note</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="export-form">
<div class="modal-body">
<div class="form-check">
<input class="form-check-input" type="radio" name="export-type" id="export-type-subtree" value="subtree">
<label class="form-check-label" for="export-type-subtree">this note and all of its descendants</label>
</div>
<div id="export-subtree-formats" class="format-choice">
<div class="form-check">
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-html"
value="html">
<label class="form-check-label" for="export-subtree-format-html">HTML in TAR archiv - this is recommended since this preserves all the formatting.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-markdown"
value="markdown-tar">
<label class="form-check-label" for="export-subtree-format-markdown">
Markdown - this preserves most of the formatting.
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-opml"
value="opml">
<label class="form-check-label" for="export-subtree-format-opml">
OPML - outliner interchange format for text only. Formatting, images and files are not included.
</label>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-type" id="export-type-single" value="single">
<label class="form-check-label" for="export-type-single">only this note without its descendants</label>
</div>
<div id="export-single-formats" class="format-choice">
<div class="form-check">
<input class="form-check-input" type="radio" name="export-single-format" id="export-single-format-html" value="html">
<label class="form-check-label" for="export-single-format-html">HTML - this is recommended since this preserves all the formatting.</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-single-format" id="export-single-format-markdown"
value="markdown-tar">
<label class="form-check-label" for="export-single-format-markdown">
Markdown - this preserves most of the formatting.
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm">Export</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,46 +0,0 @@
<div id="export-subtree-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Export subtree</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form id="export-subtree-form">
<div class="modal-body">
<div>Export note "<span class="note-title"></span>" and its subtree in the following format:</div>
<br/>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-format" id="export-format-tar" value="native-tar" checked>
<label class="form-check-label" for="export-format-tar">Native TAR - this is Trilium's native format which preserves all notes' data & metadata.</label>
</div>
<br/>
<div class="form-check">
<input class="form-check-input" type="radio" name="export-format" id="export-format-opml" value="opml">
<label class="form-check-label" for="export-format-opml">
OPML - standard outliner interchange format for text only. Formatting, images, files are not included.
</label>
</div>
<br/>
<div class="form-check disabled">
<input class="form-check-input" type="radio" name="export-format" id="export-format-markdown"
value="markdown-tar">
<label class="form-check-label" for="export-format-markdown">
Markdown - TAR archive of Markdown formatted notes
</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm">Export</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -157,9 +157,9 @@
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" id="show-note-revisions-button" data-bind="css: { disabled: type() == 'file' || type() == 'image' }">Revisions</a>
<a class="dropdown-item show-attributes-button"><kbd>Alt+A</kbd> Attributes</a>
<a class="dropdown-item" id="show-source-button" data-bind="css: { disabled: type() != 'text' }">HTML source</a>
<a class="dropdown-item" id="show-source-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' && type() != 'relation-map' && type() != 'search' }">Note source</a>
<a class="dropdown-item" id="upload-file-button">Upload file</a>
<a class="dropdown-item" id="export-note-to-markdown-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' }">Export as markdown</a>
<a class="dropdown-item" id="export-note-button" data-bind="css: { disabled: type() != 'text' }">Export note</a>
</div>
</div>
</div>
@ -173,7 +173,7 @@
<% include dialogs/attributes.ejs %>
<% include dialogs/branch_prefix.ejs %>
<% include dialogs/event_log.ejs %>
<% include dialogs/export_subtree.ejs %>
<% include dialogs/export.ejs %>
<% include dialogs/jump_to_note.ejs %>
<% include dialogs/markdown_import.ejs %>
<% include dialogs/note_revisions.ejs %>