mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02:00
Merge remote-tracking branch 'origin/stable'
This commit is contained in:
commit
b51f5ac6fd
@ -251,8 +251,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
this.triggerCommand('setActiveScreen', {screen:'detail'});
|
this.triggerCommand('setActiveScreen', {screen:'detail'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expand: (event, data) => this.setExpandedToServer(data.node.data.branchId, true),
|
expand: (event, data) => this.setExpanded(data.node.data.branchId, true),
|
||||||
collapse: (event, data) => this.setExpandedToServer(data.node.data.branchId, false),
|
collapse: (event, data) => this.setExpanded(data.node.data.branchId, false),
|
||||||
hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() },
|
hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() },
|
||||||
dnd5: {
|
dnd5: {
|
||||||
autoExpandMS: 600,
|
autoExpandMS: 600,
|
||||||
@ -932,12 +932,13 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExpandedToServer(branchId, isExpanded) {
|
async setExpanded(branchId, isExpanded) {
|
||||||
utils.assertArguments(branchId);
|
utils.assertArguments(branchId);
|
||||||
|
|
||||||
const expandedNum = isExpanded ? 1 : 0;
|
const branch = treeCache.getBranch(branchId);
|
||||||
|
branch.isExpanded = isExpanded;
|
||||||
|
|
||||||
await server.put('branches/' + branchId + '/expanded/' + expandedNum);
|
await server.put(`branches/${branchId}/expanded/${isExpanded ? 1 : 0}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadTreeFromCache() {
|
async reloadTreeFromCache() {
|
||||||
@ -997,7 +998,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
for (const shortcut of action.effectiveShortcuts) {
|
for (const shortcut of action.effectiveShortcuts) {
|
||||||
hotKeyMap[utils.normalizeShortcut(shortcut)] = node => {
|
hotKeyMap[utils.normalizeShortcut(shortcut)] = node => {
|
||||||
@ -1022,83 +1023,83 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
async deleteNotesCommand({node}) {
|
async deleteNotesCommand({node}) {
|
||||||
const branchIds = this.getSelectedOrActiveBranchIds(node);
|
const branchIds = this.getSelectedOrActiveBranchIds(node);
|
||||||
|
|
||||||
await branchService.deleteNotes(branchIds);
|
await branchService.deleteNotes(branchIds);
|
||||||
|
|
||||||
this.clearSelectedNodes();
|
this.clearSelectedNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteUpCommand({node}) {
|
moveNoteUpCommand({node}) {
|
||||||
const beforeNode = node.getPrevSibling();
|
const beforeNode = node.getPrevSibling();
|
||||||
|
|
||||||
if (beforeNode !== null) {
|
if (beforeNode !== null) {
|
||||||
branchService.moveBeforeBranch([node.data.branchId], beforeNode.data.branchId);
|
branchService.moveBeforeBranch([node.data.branchId], beforeNode.data.branchId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteDownCommand({node}) {
|
moveNoteDownCommand({node}) {
|
||||||
const afterNode = node.getNextSibling();
|
const afterNode = node.getNextSibling();
|
||||||
if (afterNode !== null) {
|
if (afterNode !== null) {
|
||||||
branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId);
|
branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteUpInHierarchyCommand({node}) {
|
moveNoteUpInHierarchyCommand({node}) {
|
||||||
branchService.moveNodeUpInHierarchy(node);
|
branchService.moveNodeUpInHierarchy(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
moveNoteDownInHierarchyCommand({node}) {
|
moveNoteDownInHierarchyCommand({node}) {
|
||||||
const toNode = node.getPrevSibling();
|
const toNode = node.getPrevSibling();
|
||||||
|
|
||||||
if (toNode !== null) {
|
if (toNode !== null) {
|
||||||
branchService.moveToParentNote([node.data.branchId], toNode.data.noteId);
|
branchService.moveToParentNote([node.data.branchId], toNode.data.noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addNoteAboveToSelectionCommand() {
|
addNoteAboveToSelectionCommand() {
|
||||||
const node = this.getFocusedNode();
|
const node = this.getFocusedNode();
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.isActive()) {
|
if (node.isActive()) {
|
||||||
node.setSelected(true);
|
node.setSelected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevSibling = node.getPrevSibling();
|
const prevSibling = node.getPrevSibling();
|
||||||
|
|
||||||
if (prevSibling) {
|
if (prevSibling) {
|
||||||
prevSibling.setActive(true, {noEvents: true});
|
prevSibling.setActive(true, {noEvents: true});
|
||||||
|
|
||||||
if (prevSibling.isSelected()) {
|
if (prevSibling.isSelected()) {
|
||||||
node.setSelected(false);
|
node.setSelected(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
prevSibling.setSelected(true);
|
prevSibling.setSelected(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addNoteBelowToSelectionCommand() {
|
addNoteBelowToSelectionCommand() {
|
||||||
const node = this.getFocusedNode();
|
const node = this.getFocusedNode();
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.isActive()) {
|
if (node.isActive()) {
|
||||||
node.setSelected(true);
|
node.setSelected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextSibling = node.getNextSibling();
|
const nextSibling = node.getNextSibling();
|
||||||
|
|
||||||
if (nextSibling) {
|
if (nextSibling) {
|
||||||
nextSibling.setActive(true, {noEvents: true});
|
nextSibling.setActive(true, {noEvents: true});
|
||||||
|
|
||||||
if (nextSibling.isSelected()) {
|
if (nextSibling.isSelected()) {
|
||||||
node.setSelected(false);
|
node.setSelected(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
nextSibling.setSelected(true);
|
nextSibling.setSelected(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1182,4 +1183,4 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
noteCreateService.duplicateNote(node.data.noteId, branch.parentNoteId);
|
noteCreateService.duplicateNote(node.data.noteId, branch.parentNoteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const noteService = require('../../services/notes');
|
|
||||||
const protectedSessionService = require('../../services/protected_session');
|
const protectedSessionService = require('../../services/protected_session');
|
||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
@ -45,7 +44,9 @@ async function downloadNoteFile(noteId, res, contentDisposition = true) {
|
|||||||
if (contentDisposition) {
|
if (contentDisposition) {
|
||||||
// (one) reason we're not using the originFileName (available as label) is that it's not
|
// (one) reason we're not using the originFileName (available as label) is that it's not
|
||||||
// available for older note revisions and thus would be inconsistent
|
// available for older note revisions and thus would be inconsistent
|
||||||
res.setHeader('Content-Disposition', utils.getContentDisposition(note.title || "untitled"));
|
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader('Content-Type', note.mime);
|
res.setHeader('Content-Type', note.mime);
|
||||||
@ -70,4 +71,4 @@ module.exports = {
|
|||||||
openFile,
|
openFile,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
downloadNoteFile
|
downloadNoteFile
|
||||||
};
|
};
|
||||||
|
@ -38,13 +38,7 @@ async function getNoteRevision(req) {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
function getRevisionFilename(noteRevision) {
|
function getRevisionFilename(noteRevision) {
|
||||||
let filename = noteRevision.title || "untitled";
|
let filename = utils.formatDownloadTitle(noteRevision.title, noteRevision.type, noteRevision.mime);
|
||||||
|
|
||||||
if (noteRevision.type === 'text') {
|
|
||||||
filename += '.html';
|
|
||||||
} else if (['relation-map', 'search'].includes(noteRevision.type)) {
|
|
||||||
filename += '.json';
|
|
||||||
}
|
|
||||||
|
|
||||||
const extension = path.extname(filename);
|
const extension = path.extname(filename);
|
||||||
const date = noteRevision.dateCreated
|
const date = noteRevision.dateCreated
|
||||||
@ -158,4 +152,4 @@ module.exports = {
|
|||||||
eraseAllNoteRevisions,
|
eraseAllNoteRevisions,
|
||||||
eraseNoteRevision,
|
eraseNoteRevision,
|
||||||
restoreNoteRevision
|
restoreNoteRevision
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ const Attribute = require('../entities/attribute');
|
|||||||
const hoistedNoteService = require('../services/hoisted_note');
|
const hoistedNoteService = require('../services/hoisted_note');
|
||||||
const protectedSessionService = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
const log = require('../services/log');
|
const log = require('../services/log');
|
||||||
|
const utils = require('../services/utils');
|
||||||
const noteRevisionService = require('../services/note_revisions');
|
const noteRevisionService = require('../services/note_revisions');
|
||||||
const attributeService = require('../services/attributes');
|
const attributeService = require('../services/attributes');
|
||||||
const request = require('./request');
|
const request = require('./request');
|
||||||
@ -276,9 +277,9 @@ async function downloadImage(noteId, imageUrl) {
|
|||||||
const downloadImagePromises = {};
|
const downloadImagePromises = {};
|
||||||
|
|
||||||
function replaceUrl(content, url, imageNote) {
|
function replaceUrl(content, url, imageNote) {
|
||||||
const quoted = url.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
|
const quotedUrl = utils.quoteRegex(url);
|
||||||
|
|
||||||
return content.replace(new RegExp(`\\s+src=[\"']${quoted}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
|
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadImages(noteId, content) {
|
async function downloadImages(noteId, content) {
|
||||||
|
@ -5,6 +5,7 @@ const randtoken = require('rand-token').generator({source: 'crypto'});
|
|||||||
const unescape = require('unescape');
|
const unescape = require('unescape');
|
||||||
const escape = require('escape-html');
|
const escape = require('escape-html');
|
||||||
const sanitize = require("sanitize-filename");
|
const sanitize = require("sanitize-filename");
|
||||||
|
const mimeTypes = require('mime-types');
|
||||||
|
|
||||||
function newEntityId() {
|
function newEntityId() {
|
||||||
return randomString(12);
|
return randomString(12);
|
||||||
@ -166,10 +167,46 @@ function isStringNote(type, mime) {
|
|||||||
|| STRING_MIME_TYPES.includes(mime);
|
|| STRING_MIME_TYPES.includes(mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceAll(string, replaceWhat, replaceWith) {
|
function quoteRegex(url) {
|
||||||
const escapedWhat = replaceWhat.replace(/([\/,!\\^${}\[\]().*+?|<>\-&])/g, "\\$&");
|
return url.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
return string.replace(new RegExp(escapedWhat, "g"), replaceWith);
|
function replaceAll(string, replaceWhat, replaceWith) {
|
||||||
|
const quotedReplaceWhat = quoteRegex(replaceWhat);
|
||||||
|
|
||||||
|
return string.replace(new RegExp(quotedReplaceWhat, "g"), replaceWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDownloadTitle(filename, type, mime) {
|
||||||
|
if (!filename) {
|
||||||
|
filename = "untitled";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'text') {
|
||||||
|
return filename + '.html';
|
||||||
|
} else if (['relation-map', 'search'].includes(type)) {
|
||||||
|
return filename + '.json';
|
||||||
|
} else {
|
||||||
|
if (!mime) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
mime = mime.toLowerCase();
|
||||||
|
const filenameLc = filename.toLowerCase();
|
||||||
|
const extensions = mimeTypes.extensions[mime];
|
||||||
|
|
||||||
|
if (!extensions || extensions.length === 0) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ext of extensions) {
|
||||||
|
if (filenameLc.endsWith('.' + ext)) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename + '.' + extensions[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -198,5 +235,7 @@ module.exports = {
|
|||||||
sanitizeFilenameForHeader,
|
sanitizeFilenameForHeader,
|
||||||
getContentDisposition,
|
getContentDisposition,
|
||||||
isStringNote,
|
isStringNote,
|
||||||
replaceAll
|
quoteRegex,
|
||||||
};
|
replaceAll,
|
||||||
|
formatDownloadTitle
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user