You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop, Cli: Fixes #9485: When importing MarkdownD+FrontMatter files that contain images with a data URL source, the import fails
This commit is contained in:
		| @@ -0,0 +1,5 @@ | ||||
| --- | ||||
| title: DataUrl image | ||||
| --- | ||||
|  | ||||
| <img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%2023%2038%22%3E%3Cpath%20d%3D%22M16.6%2038.1h-5.5l-.2-2.9-.2%202.9h-5.5L5%2025.3l-.8%202a1.53%201.53%200%2001-1.9.9l-1.2-.4a1.58%201.58%200%2001-1-1.9v-.1c.3-.9%203.1-11.2%203.1-11.2a2.66%202.66%200%20012.3-2l.6-.5a6.93%206.93%200%20014.7-12%206.8%206.8%200%20014.9%202%207%207%200%20012%204.9%206.65%206.65%200%2001-2.2%205l.7.5a2.78%202.78%200%20012.4%202s2.9%2011.2%202.9%2011.3a1.53%201.53%200%2001-.9%201.9l-1.3.4a1.63%201.63%200%2001-1.9-.9l-.7-1.8-.1%2012.7zm-3.6-2h1.7L14.9%2020.3l1.9-.3%202.4%206.3.3-.1c-.2-.8-.8-3.2-2.8-10.9a.63.63%200%2000-.6-.5h-.6l-1.1-.9h-1.9l-.3-2a4.83%204.83%200%20003.5-4.7A4.78%204.78%200%200011%202.3H10.8a4.9%204.9%200%2000-1.4%209.6l-.3%202h-1.9l-1%20.9h-.6a.74.74%200%2000-.6.5c-2%207.5-2.7%2010-3%2010.9l.3.1L4.8%2020l1.9.3.2%2015.8h1.6l.6-8.4a1.52%201.52%200%20011.5-1.4%201.5%201.5%200%20011.5%201.4l.9%208.4zm-10.9-9.6zm17.5-.1z%22%20style%3D%22isolation%3Aisolate%22%20fill%3D%22%23333%22%20opacity%3D%22.7%22/%3E%3Cpath%20d%3D%22M5.9%2013.6l1.1-.9h7.8l1.2.9%22%20fill%3D%22%23ce592c%22/%3E%3Cellipse%20cx%3D%2210.9%22%20cy%3D%2213.1%22%20rx%3D%222.7%22%20ry%3D%22.3%22%20style%3D%22isolation%3Aisolate%22%20fill%3D%22%23ce592c%22%20opacity%3D%22.5%22/%3E%3Cpath%20d%3D%22M20.6%2026.1l-2.9-11.3a1.71%201.71%200%2000-1.6-1.2H5.699999999999999a1.69%201.69%200%2000-1.5%201.3l-3.1%2011.3a.61.61%200%2000.3.7l1.1.4a.61.61%200%2000.7-.3l2.7-6.7.2%2016.8h3.6l.6-9.3a.47.47%200%2001.44-.5h.06c.4%200%20.4.2.5.5l.6%209.3h3.6L15.7%2020.3l2.5%206.6a.52.52%200%2000.66.31l1.2-.4a.57.57%200%2000.5-.7z%22%20fill%3D%22%23fdbf2d%22/%3E%3Cpath%20d%3D%22M7%2013.6l3.9%206.7%203.9-6.7%22%20style%3D%22isolation%3Aisolate%22%20fill%3D%22%23cf572e%22%20opacity%3D%22.6%22/%3E%3Ccircle%20cx%3D%2210.9%22%20cy%3D%227%22%20r%3D%225.9%22%20fill%3D%22%23fdbf2d%22/%3E%3C/svg%3E" alt="Street View Pegman Control" style="height:30px;width:30px;position:absolute;transform:translate(-50%,-50%);pointer-events:none"> | ||||
| @@ -12,6 +12,7 @@ import htmlUtils from '../../htmlUtils'; | ||||
| import { unique } from '../../ArrayUtils'; | ||||
| const { pregQuote } = require('../../string-utils-common'); | ||||
| import { MarkupToHtml } from '@joplin/renderer'; | ||||
| import { isDataUrl } from '@joplin/utils/url'; | ||||
|  | ||||
| export default class InteropService_Importer_Md extends InteropService_Importer_Base { | ||||
| 	protected importedNotes: Record<string, NoteEntity> = {}; | ||||
| @@ -109,43 +110,49 @@ export default class InteropService_Importer_Md extends InteropService_Importer_ | ||||
| 		const fileLinks = unique(markdownLinks.concat(htmlLinks)); | ||||
| 		for (const encodedLink of fileLinks) { | ||||
| 			const link = decodeURI(encodedLink); | ||||
| 			// Handle anchor links appropriately | ||||
| 			const trimmedLink = this.trimAnchorLink(link); | ||||
| 			const attachmentPath = filename(`${dirname(filePath)}/${trimmedLink}`, true); | ||||
| 			const pathWithExtension = `${attachmentPath}.${fileExtension(trimmedLink)}`; | ||||
| 			const stat = await shim.fsDriver().stat(pathWithExtension); | ||||
| 			const isDir = stat ? stat.isDirectory() : false; | ||||
| 			if (stat && !isDir) { | ||||
| 				const supportedFileExtension = this.metadata().fileExtensions; | ||||
| 				const resolvedPath = shim.fsDriver().resolve(pathWithExtension); | ||||
| 				let id = ''; | ||||
| 				// If the link looks like a note, then import it | ||||
| 				if (supportedFileExtension.indexOf(fileExtension(trimmedLink).toLowerCase()) >= 0) { | ||||
| 					// If the note hasn't been imported yet, do so now | ||||
| 					if (!this.importedNotes[resolvedPath]) { | ||||
| 						await this.importFile(resolvedPath, parentFolderId); | ||||
|  | ||||
| 			if (isDataUrl(link)) { | ||||
| 				// Just leave it as it is. We could potentially import | ||||
| 				// it as a resource but for now that's good enough. | ||||
| 			} else { | ||||
| 				// Handle anchor links appropriately | ||||
| 				const trimmedLink = this.trimAnchorLink(link); | ||||
| 				const attachmentPath = filename(`${dirname(filePath)}/${trimmedLink}`, true); | ||||
| 				const pathWithExtension = `${attachmentPath}.${fileExtension(trimmedLink)}`; | ||||
| 				const stat = await shim.fsDriver().stat(pathWithExtension); | ||||
| 				const isDir = stat ? stat.isDirectory() : false; | ||||
| 				if (stat && !isDir) { | ||||
| 					const supportedFileExtension = this.metadata().fileExtensions; | ||||
| 					const resolvedPath = shim.fsDriver().resolve(pathWithExtension); | ||||
| 					let id = ''; | ||||
| 					// If the link looks like a note, then import it | ||||
| 					if (supportedFileExtension.indexOf(fileExtension(trimmedLink).toLowerCase()) >= 0) { | ||||
| 						// If the note hasn't been imported yet, do so now | ||||
| 						if (!this.importedNotes[resolvedPath]) { | ||||
| 							await this.importFile(resolvedPath, parentFolderId); | ||||
| 						} | ||||
|  | ||||
| 						id = this.importedNotes[resolvedPath].id; | ||||
| 					} else { | ||||
| 						const resource = await shim.createResourceFromPath(pathWithExtension); | ||||
| 						id = resource.id; | ||||
| 					} | ||||
|  | ||||
| 					id = this.importedNotes[resolvedPath].id; | ||||
| 				} else { | ||||
| 					const resource = await shim.createResourceFromPath(pathWithExtension); | ||||
| 					id = resource.id; | ||||
| 				} | ||||
| 					// The first is a normal link, the second is supports the <link> and [](<link with spaces>) syntax | ||||
| 					// Only opening patterns are consider in order to cover all occurances | ||||
| 					// We need to use the encoded link as well because some links (link's with spaces) | ||||
| 					// will appear encoded in the source. Other links (unicode chars) will not | ||||
| 					const linksToReplace = [this.trimAnchorLink(link), this.trimAnchorLink(encodedLink)]; | ||||
|  | ||||
| 				// The first is a normal link, the second is supports the <link> and [](<link with spaces>) syntax | ||||
| 				// Only opening patterns are consider in order to cover all occurances | ||||
| 				// We need to use the encoded link as well because some links (link's with spaces) | ||||
| 				// will appear encoded in the source. Other links (unicode chars) will not | ||||
| 				const linksToReplace = [this.trimAnchorLink(link), this.trimAnchorLink(encodedLink)]; | ||||
| 					for (let j = 0; j < linksToReplace.length; j++) { | ||||
| 						const linkToReplace = pregQuote(linksToReplace[j]); | ||||
|  | ||||
| 				for (let j = 0; j < linksToReplace.length; j++) { | ||||
| 					const linkToReplace = pregQuote(linksToReplace[j]); | ||||
| 						// Markdown links | ||||
| 						updated = markdownUtils.replaceResourceUrl(updated, linkToReplace, id); | ||||
|  | ||||
| 					// Markdown links | ||||
| 					updated = markdownUtils.replaceResourceUrl(updated, linkToReplace, id); | ||||
|  | ||||
| 					// HTML links | ||||
| 					updated = htmlUtils.replaceResourceUrl(updated, linkToReplace, id); | ||||
| 						// HTML links | ||||
| 						updated = htmlUtils.replaceResourceUrl(updated, linkToReplace, id); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import { setupDatabaseAndSynchronizer, supportDir, switchClient } from '../../te | ||||
| import { ImportModuleOutputFormat, ImportOptions } from './types'; | ||||
| import InteropService from './InteropService'; | ||||
| import Folder from '../../models/Folder'; | ||||
| import { NoteEntity } from '../database/types'; | ||||
|  | ||||
| async function importNote(path: string) { | ||||
| 	const folder = await Folder.save({}); | ||||
| @@ -21,7 +22,7 @@ async function importNote(path: string) { | ||||
| 	return allNotes[0]; | ||||
| } | ||||
|  | ||||
| const importTestFile = async (name: string) => { | ||||
| const importTestFile = async (name: string): Promise<NoteEntity> => { | ||||
| 	return importNote(`${supportDir}/test_notes/yaml/${name}`); | ||||
| }; | ||||
|  | ||||
| @@ -160,4 +161,9 @@ describe('InteropService_Importer_Md_frontmatter: importMetadata', () => { | ||||
| 		const note = await importTestFile('title_start_with_dash.md'); | ||||
| 		expect(note.title).toBe('-Start with dash'); | ||||
| 	}); | ||||
|  | ||||
| 	it('should import a note that contains an image in DataUrl format', async () => { | ||||
| 		const note = await importTestFile('note_with_dataurl_image.md'); | ||||
| 		expect(note.body).toBe('<img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%2023%2038%22%3E%3Cpath%20d%3D%22M16.6%2038.1h-5.5l-.2-2.9-.2%202.9h-5.5L5%2025.3l-.8%202a1.53%201.53%200%2001-1.9.9l-1.2-.4a1.58%201.58%200%2001-1-1.9v-.1c.3-.9%203.1-11.2%203.1-11.2a2.66%202.66%200%20012.3-2l.6-.5a6.93%206.93%200%20014.7-12%206.8%206.8%200%20014.9%202%207%207%200%20012%204.9%206.65%206.65%200%2001-2.2%205l.7.5a2.78%202.78%200%20012.4%202s2.9%2011.2%202.9%2011.3a1.53%201.53%200%2001-.9%201.9l-1.3.4a1.63%201.63%200%2001-1.9-.9l-.7-1.8-.1%2012.7zm-3.6-2h1.7L14.9%2020.3l1.9-.3%202.4%206.3.3-.1c-.2-.8-.8-3.2-2.8-10.9a.63.63%200%2000-.6-.5h-.6l-1.1-.9h-1.9l-.3-2a4.83%204.83%200%20003.5-4.7A4.78%204.78%200%200011%202.3H10.8a4.9%204.9%200%2000-1.4%209.6l-.3%202h-1.9l-1%20.9h-.6a.74.74%200%2000-.6.5c-2%207.5-2.7%2010-3%2010.9l.3.1L4.8%2020l1.9.3.2%2015.8h1.6l.6-8.4a1.52%201.52%200%20011.5-1.4%201.5%201.5%200%20011.5%201.4l.9%208.4zm-10.9-9.6zm17.5-.1z%22%20style%3D%22isolation%3Aisolate%22%20fill%3D%22%23333%22%20opacity%3D%22.7%22/%3E%3Cpath%20d%3D%22M5.9%2013.6l1.1-.9h7.8l1.2.9%22%20fill%3D%22%23ce592c%22/%3E%3Cellipse%20cx%3D%2210.9%22%20cy%3D%2213.1%22%20rx%3D%222.7%22%20ry%3D%22.3%22%20style%3D%22isolation%3Aisolate%22%20fill%3D%22%23ce592c%22%20opacity%3D%22.5%22/%3E%3Cpath%20d%3D%22M20.6%2026.1l-2.9-11.3a1.71%201.71%200%2000-1.6-1.2H5.699999999999999a1.69%201.69%200%2000-1.5%201.3l-3.1%2011.3a.61.61%200%2000.3.7l1.1.4a.61.61%200%2000.7-.3l2.7-6.7.2%2016.8h3.6l.6-9.3a.47.47%200%2001.44-.5h.06c.4%200%20.4.2.5.5l.6%209.3h3.6L15.7%2020.3l2.5%206.6a.52.52%200%2000.66.31l1.2-.4a.57.57%200%2000.5-.7z%22%20fill%3D%22%23fdbf2d%22/%3E%3Cpath%20d%3D%22M7%2013.6l3.9%206.7%203.9-6.7%22%20style%3D%22isolation%3Aisolate%22%20fill%3D%22%23cf572e%22%20opacity%3D%22.6%22/%3E%3Ccircle%20cx%3D%2210.9%22%20cy%3D%227%22%20r%3D%225.9%22%20fill%3D%22%23fdbf2d%22/%3E%3C/svg%3E" alt="Street View Pegman Control" style="height:30px;width:30px;position:absolute;transform:translate(-50%,-50%);pointer-events:none">'); | ||||
| 	}); | ||||
| }); | ||||
|   | ||||
| @@ -96,3 +96,7 @@ export const fileUriToPath = (path: string, platform = 'linux') => { | ||||
|  | ||||
| 	return output; | ||||
| }; | ||||
|  | ||||
| export const isDataUrl = (path: string) => { | ||||
| 	return path.startsWith('data:'); | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user