Merge remote-tracking branch 'origin/stable'

This commit is contained in:
zadam 2020-05-12 10:59:02 +02:00
commit b51f5ac6fd
5 changed files with 81 additions and 45 deletions

View File

@ -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);
} }
} }

View File

@ -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
}; };

View File

@ -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
}; };

View File

@ -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) {

View File

@ -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
};