mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-01 05:21:32 +08: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
	 zadam
						zadam