mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/stable'
This commit is contained in:
		| @@ -251,8 +251,8 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|                     this.triggerCommand('setActiveScreen', {screen:'detail'}); | ||||
|                 } | ||||
|             }, | ||||
|             expand: (event, data) => this.setExpandedToServer(data.node.data.branchId, true), | ||||
|             collapse: (event, data) => this.setExpandedToServer(data.node.data.branchId, false), | ||||
|             expand: (event, data) => this.setExpanded(data.node.data.branchId, true), | ||||
|             collapse: (event, data) => this.setExpanded(data.node.data.branchId, false), | ||||
|             hotkeys: utils.isMobile() ? undefined : { keydown: await this.getHotKeys() }, | ||||
|             dnd5: { | ||||
|                 autoExpandMS: 600, | ||||
| @@ -932,12 +932,13 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async setExpandedToServer(branchId, isExpanded) { | ||||
|     async setExpanded(branchId, isExpanded) { | ||||
|         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() { | ||||
| @@ -997,7 +998,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|                 return false; | ||||
|             } | ||||
|         }; | ||||
|          | ||||
|  | ||||
|         for (const action of actions) { | ||||
|             for (const shortcut of action.effectiveShortcuts) { | ||||
|                 hotKeyMap[utils.normalizeShortcut(shortcut)] = node => { | ||||
| @@ -1022,83 +1023,83 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|  | ||||
|     async deleteNotesCommand({node}) { | ||||
|         const branchIds = this.getSelectedOrActiveBranchIds(node); | ||||
|      | ||||
|  | ||||
|         await branchService.deleteNotes(branchIds); | ||||
|  | ||||
|         this.clearSelectedNodes(); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     moveNoteUpCommand({node}) { | ||||
|         const beforeNode = node.getPrevSibling(); | ||||
|      | ||||
|  | ||||
|         if (beforeNode !== null) { | ||||
|             branchService.moveBeforeBranch([node.data.branchId], beforeNode.data.branchId); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
|     moveNoteDownCommand({node}) { | ||||
|         const afterNode = node.getNextSibling(); | ||||
|         if (afterNode !== null) { | ||||
|             branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
|     moveNoteUpInHierarchyCommand({node}) { | ||||
|         branchService.moveNodeUpInHierarchy(node); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     moveNoteDownInHierarchyCommand({node}) { | ||||
|         const toNode = node.getPrevSibling(); | ||||
|      | ||||
|  | ||||
|         if (toNode !== null) { | ||||
|             branchService.moveToParentNote([node.data.branchId], toNode.data.noteId); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|  | ||||
|     addNoteAboveToSelectionCommand() { | ||||
|         const node = this.getFocusedNode(); | ||||
|      | ||||
|  | ||||
|         if (!node) { | ||||
|             return; | ||||
|         } | ||||
|      | ||||
|  | ||||
|         if (node.isActive()) { | ||||
|             node.setSelected(true); | ||||
|         } | ||||
|      | ||||
|  | ||||
|         const prevSibling = node.getPrevSibling(); | ||||
|      | ||||
|  | ||||
|         if (prevSibling) { | ||||
|             prevSibling.setActive(true, {noEvents: true}); | ||||
|      | ||||
|  | ||||
|             if (prevSibling.isSelected()) { | ||||
|                 node.setSelected(false); | ||||
|             } | ||||
|      | ||||
|  | ||||
|             prevSibling.setSelected(true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     addNoteBelowToSelectionCommand() { | ||||
|         const node = this.getFocusedNode(); | ||||
|      | ||||
|  | ||||
|         if (!node) { | ||||
|             return; | ||||
|         } | ||||
|      | ||||
|  | ||||
|         if (node.isActive()) { | ||||
|             node.setSelected(true); | ||||
|         } | ||||
|      | ||||
|  | ||||
|         const nextSibling = node.getNextSibling(); | ||||
|      | ||||
|  | ||||
|         if (nextSibling) { | ||||
|             nextSibling.setActive(true, {noEvents: true}); | ||||
|      | ||||
|  | ||||
|             if (nextSibling.isSelected()) { | ||||
|                 node.setSelected(false); | ||||
|             } | ||||
|      | ||||
|  | ||||
|             nextSibling.setSelected(true); | ||||
|         } | ||||
|     } | ||||
| @@ -1182,4 +1183,4 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|  | ||||
|         noteCreateService.duplicateNote(node.data.noteId, branch.parentNoteId); | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const noteService = require('../../services/notes'); | ||||
| const protectedSessionService = require('../../services/protected_session'); | ||||
| const repository = require('../../services/repository'); | ||||
| const utils = require('../../services/utils'); | ||||
| @@ -45,7 +44,9 @@ async function downloadNoteFile(noteId, res, contentDisposition = true) { | ||||
|     if (contentDisposition) { | ||||
|         // (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 | ||||
|         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); | ||||
| @@ -70,4 +71,4 @@ module.exports = { | ||||
|     openFile, | ||||
|     downloadFile, | ||||
|     downloadNoteFile | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -38,13 +38,7 @@ async function getNoteRevision(req) { | ||||
|  * @return {string} | ||||
|  */ | ||||
| function getRevisionFilename(noteRevision) { | ||||
|     let filename = noteRevision.title || "untitled"; | ||||
|  | ||||
|     if (noteRevision.type === 'text') { | ||||
|         filename += '.html'; | ||||
|     } else if (['relation-map', 'search'].includes(noteRevision.type)) { | ||||
|         filename += '.json'; | ||||
|     } | ||||
|     let filename = utils.formatDownloadTitle(noteRevision.title, noteRevision.type, noteRevision.mime); | ||||
|  | ||||
|     const extension = path.extname(filename); | ||||
|     const date = noteRevision.dateCreated | ||||
| @@ -158,4 +152,4 @@ module.exports = { | ||||
|     eraseAllNoteRevisions, | ||||
|     eraseNoteRevision, | ||||
|     restoreNoteRevision | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -13,6 +13,7 @@ const Attribute = require('../entities/attribute'); | ||||
| const hoistedNoteService = require('../services/hoisted_note'); | ||||
| const protectedSessionService = require('../services/protected_session'); | ||||
| const log = require('../services/log'); | ||||
| const utils = require('../services/utils'); | ||||
| const noteRevisionService = require('../services/note_revisions'); | ||||
| const attributeService = require('../services/attributes'); | ||||
| const request = require('./request'); | ||||
| @@ -276,9 +277,9 @@ async function downloadImage(noteId, imageUrl) { | ||||
| const downloadImagePromises = {}; | ||||
|  | ||||
| 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) { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ const randtoken = require('rand-token').generator({source: 'crypto'}); | ||||
| const unescape = require('unescape'); | ||||
| const escape = require('escape-html'); | ||||
| const sanitize = require("sanitize-filename"); | ||||
| const mimeTypes = require('mime-types'); | ||||
|  | ||||
| function newEntityId() { | ||||
|     return randomString(12); | ||||
| @@ -166,10 +167,46 @@ function isStringNote(type, mime) { | ||||
|         || STRING_MIME_TYPES.includes(mime); | ||||
| } | ||||
|  | ||||
| function replaceAll(string, replaceWhat, replaceWith) { | ||||
|     const escapedWhat = replaceWhat.replace(/([\/,!\\^${}\[\]().*+?|<>\-&])/g, "\\$&"); | ||||
| function quoteRegex(url) { | ||||
|     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 = { | ||||
| @@ -198,5 +235,7 @@ module.exports = { | ||||
|     sanitizeFilenameForHeader, | ||||
|     getContentDisposition, | ||||
|     isStringNote, | ||||
|     replaceAll | ||||
| }; | ||||
|     quoteRegex, | ||||
|     replaceAll, | ||||
|     formatDownloadTitle | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user