mirror of
https://github.com/zadam/trilium.git
synced 2025-06-05 17:38:47 +02:00
unified export dialog, WIP
This commit is contained in:
parent
3e351bd8d3
commit
ee23bcc783
67
src/public/javascripts/dialogs/export.js
Normal file
67
src/public/javascripts/dialogs/export.js
Normal 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
|
||||
};
|
@ -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
|
||||
};
|
5
src/public/javascripts/services/bootstrap.js
vendored
5
src/public/javascripts/services/bootstrap.js
vendored
@ -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();
|
||||
|
@ -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
|
||||
};
|
@ -564,8 +564,6 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
|
||||
|
||||
clearSelectedNodes(); // to unmark previously active node
|
||||
|
||||
infoService.showMessage("Created!");
|
||||
|
||||
return {note, branch};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
67
src/views/dialogs/export.ejs
Normal file
67
src/views/dialogs/export.ejs
Normal 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">×</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>
|
@ -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">×</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>
|
@ -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 %>
|
||||
|
Loading…
x
Reference in New Issue
Block a user