You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Fix import interop service (#1887)
* Revert "Revert "Desktop: Add ENEX to HTML export (#1795)"" This reverts commit50b66cceca. * Revert "Revert "Desktop, Cli: Fixed interop service so that it still allow auto-detecting importer based on format (required for Cli and for test units)"" This reverts commitc7c57ab2a5. * Fix the .md importer * Add comment re future refactor * Rm importerClass for .md importer * Fix EnexToMd module name
This commit is contained in:
		
				
					committed by
					
						 Laurent Cozic
						Laurent Cozic
					
				
			
			
				
	
			
			
			
						parent
						
							8a8ecaade3
						
					
				
				
					commit
					172d925f0f
				
			
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -42,4 +42,5 @@ ReactNativeClient/lib/csstojs/ | ||||
| ReactNativeClient/lib/rnInjectedJs/ | ||||
| ElectronClient/app/gui/note-viewer/fonts/ | ||||
| ElectronClient/app/gui/note-viewer/lib.js | ||||
| Tools/commit_hook.txt | ||||
| Tools/commit_hook.txt | ||||
| .vscode/* | ||||
|   | ||||
							
								
								
									
										106
									
								
								CliClient/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										106
									
								
								CliClient/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -312,6 +312,22 @@ | ||||
|         "source-map": "0.5.x" | ||||
|       } | ||||
|     }, | ||||
|     "clean-html": { | ||||
|       "version": "1.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/clean-html/-/clean-html-1.5.0.tgz", | ||||
|       "integrity": "sha512-eDu0vN44ZBvoEU0oRIKwWPIccGWXtdnUNmKJuTukZ1de00Uoqavb5pfIMKiC7/r+knQ5RbvAjGuVZiN3JwJL4Q==", | ||||
|       "requires": { | ||||
|         "htmlparser2": "^3.8.2", | ||||
|         "minimist": "^1.1.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "minimist": { | ||||
|           "version": "1.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", | ||||
|           "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "cliss": { | ||||
|       "version": "0.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/cliss/-/cliss-0.0.2.tgz", | ||||
| @@ -550,6 +566,32 @@ | ||||
|       "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz", | ||||
|       "integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==" | ||||
|     }, | ||||
|     "dom-serializer": { | ||||
|       "version": "0.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", | ||||
|       "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==", | ||||
|       "requires": { | ||||
|         "domelementtype": "^2.0.1", | ||||
|         "entities": "^2.0.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "domelementtype": { | ||||
|           "version": "2.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", | ||||
|           "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==" | ||||
|         }, | ||||
|         "entities": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", | ||||
|           "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "domelementtype": { | ||||
|       "version": "1.3.1", | ||||
|       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", | ||||
|       "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" | ||||
|     }, | ||||
|     "domexception": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", | ||||
| @@ -558,6 +600,23 @@ | ||||
|         "webidl-conversions": "^4.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "domhandler": { | ||||
|       "version": "2.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", | ||||
|       "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", | ||||
|       "requires": { | ||||
|         "domelementtype": "1" | ||||
|       } | ||||
|     }, | ||||
|     "domutils": { | ||||
|       "version": "1.7.0", | ||||
|       "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", | ||||
|       "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", | ||||
|       "requires": { | ||||
|         "dom-serializer": "0", | ||||
|         "domelementtype": "1" | ||||
|       } | ||||
|     }, | ||||
|     "ecc-jsbn": { | ||||
|       "version": "0.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", | ||||
| @@ -1015,22 +1074,41 @@ | ||||
|         "uglify-js": "3.3.x" | ||||
|       } | ||||
|     }, | ||||
|     "http-errors": { | ||||
|       "version": "1.7.3", | ||||
|       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", | ||||
|       "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", | ||||
|     "htmlparser2": { | ||||
|       "version": "3.10.1", | ||||
|       "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", | ||||
|       "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", | ||||
|       "requires": { | ||||
|         "depd": "~1.1.2", | ||||
|         "inherits": "2.0.4", | ||||
|         "setprototypeof": "1.1.1", | ||||
|         "statuses": ">= 1.5.0 < 2", | ||||
|         "toidentifier": "1.0.0" | ||||
|         "domelementtype": "^1.3.1", | ||||
|         "domhandler": "^2.3.0", | ||||
|         "domutils": "^1.5.1", | ||||
|         "entities": "^1.1.1", | ||||
|         "inherits": "^2.0.1", | ||||
|         "readable-stream": "^3.1.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "inherits": { | ||||
|           "version": "2.0.4", | ||||
|           "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", | ||||
|           "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" | ||||
|         "readable-stream": { | ||||
|           "version": "3.4.0", | ||||
|           "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", | ||||
|           "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", | ||||
|           "requires": { | ||||
|             "inherits": "^2.0.3", | ||||
|             "string_decoder": "^1.1.1", | ||||
|             "util-deprecate": "^1.0.1" | ||||
|           } | ||||
|         }, | ||||
|         "safe-buffer": { | ||||
|           "version": "5.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", | ||||
|           "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" | ||||
|         }, | ||||
|         "string_decoder": { | ||||
|           "version": "1.3.0", | ||||
|           "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | ||||
|           "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | ||||
|           "requires": { | ||||
|             "safe-buffer": "~5.2.0" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| @@ -3130,7 +3208,7 @@ | ||||
|       "requires": { | ||||
|         "chalk": "^2.1.0", | ||||
|         "emphasize": "^1.5.0", | ||||
|         "node-emoji": "git+https://github.com/laurent22/node-emoji.git", | ||||
|         "node-emoji": "git+https://github.com/laurent22/node-emoji.git#9fa01eac463e94dde1316ef8c53089eeef4973b5", | ||||
|         "slice-ansi": "^1.0.0", | ||||
|         "string-width": "^2.1.1", | ||||
|         "terminal-kit": "^1.13.11", | ||||
|   | ||||
| @@ -31,6 +31,7 @@ | ||||
|     "app-module-path": "^2.2.0", | ||||
|     "async-mutex": "^0.1.3", | ||||
|     "base-64": "^0.1.0", | ||||
|     "clean-html": "^1.5.0", | ||||
|     "compare-version": "^0.1.2", | ||||
|     "diacritics": "^1.3.0", | ||||
|     "diff-match-patch": "^1.0.4", | ||||
|   | ||||
							
								
								
									
										108
									
								
								CliClient/tests/EnexToHtml.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								CliClient/tests/EnexToHtml.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| require('app-module-path').addPath(__dirname); | ||||
|  | ||||
| const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js'); | ||||
| const { shim } = require('lib/shim'); | ||||
| const { enexXmlToHtml } = require('lib/import-enex-html-gen.js'); | ||||
|  | ||||
| jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 60 * 1000; // Can run for a while since everything is in the same test unit | ||||
|  | ||||
| process.on('unhandledRejection', (reason, p) => { | ||||
| 	console.warn('Unhandled Rejection at: Promise', p, 'reason:', reason); | ||||
| }); | ||||
|  | ||||
| const fileWithPath = (filename) => | ||||
| 	`${__dirname}/enex_to_html/${filename}`; | ||||
|  | ||||
| const audioResource = { | ||||
| 	filename: 'audio test', | ||||
| 	id: '9168ee833d03c5ea7c730ac6673978c1', | ||||
| 	mime: 'audio/x-m4a', | ||||
| 	size: 82011, | ||||
| 	title: 'audio test', | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Tests the importer for a single note, checking that the result of | ||||
|  * processing the given `.enex` input file matches the contents of the given | ||||
|  * `.html` file. | ||||
|  * | ||||
|  * Note that this does not test the importing of an entire exported `.enex` | ||||
|  * archive, but rather a single node of such a file. Thus, the test data files | ||||
|  * (e.g. `./enex_to_html/code1.enex`) correspond to the contents of a single | ||||
|  * `<note>...</note>` node in an `.enex` file already extracted from | ||||
|  * `<content><![CDATA[...]]</content>`. | ||||
|  */ | ||||
| const compareOutputToExpected = (options) => { | ||||
| 	const inputFile = fileWithPath(`${options.testName}.enex`); | ||||
| 	const outputFile = fileWithPath(`${options.testName}.html`); | ||||
| 	const testTitle = `should convert from Enex to Html: ${options.testName}`; | ||||
|  | ||||
| 	it(testTitle, asyncTest(async () => { | ||||
| 		const enexInput = await shim.fsDriver().readFile(inputFile); | ||||
| 		const expectedOutput = await shim.fsDriver().readFile(outputFile); | ||||
| 		const actualOutput = await enexXmlToHtml(enexInput, options.resources); | ||||
|  | ||||
| 		expect(actualOutput).toEqual(expectedOutput); | ||||
| 	})); | ||||
| }; | ||||
|  | ||||
| describe('EnexToHtml', function() { | ||||
| 	beforeEach(async (done) => { | ||||
| 		await setupDatabaseAndSynchronizer(1); | ||||
| 		await switchClient(1); | ||||
| 		done(); | ||||
| 	}); | ||||
|  | ||||
| 	compareOutputToExpected({ | ||||
| 		testName: 'checklist-list', | ||||
| 		resources: [], | ||||
| 	}); | ||||
|  | ||||
| 	compareOutputToExpected({ | ||||
| 		testName: 'svg', | ||||
| 		resources: [], | ||||
| 	}); | ||||
|  | ||||
| 	compareOutputToExpected({ | ||||
| 		testName: 'en-media--image', | ||||
| 		resources: [{ | ||||
| 			filename: '', | ||||
| 			id: '89ce7da62c6b2832929a6964237e98e9', // Mock id | ||||
| 			mime: 'image/jpeg', | ||||
| 			size: 50347, | ||||
| 			title: '', | ||||
| 		}], | ||||
| 	}); | ||||
|  | ||||
| 	compareOutputToExpected({ | ||||
| 		testName: 'en-media--audio', | ||||
| 		resources: [audioResource], | ||||
| 	}); | ||||
|  | ||||
| 	compareOutputToExpected({ | ||||
| 		testName: 'attachment', | ||||
| 		resources: [{ | ||||
| 			filename: 'attachment-1', | ||||
| 			id: '21ca2b948f222a38802940ec7e2e5de3', | ||||
| 			mime: 'application/pdf', // Any non-image/non-audio mime type will do | ||||
| 			size: 1000, | ||||
| 		}], | ||||
| 	}); | ||||
|  | ||||
| 	it('fails when not given a matching resource', asyncTest(async () => { | ||||
| 		// To test the promise-unexpectedly-resolved case, add `audioResource` to the array. | ||||
| 		const resources = []; | ||||
| 		const inputFile = fileWithPath('en-media--image.enex'); | ||||
| 		const enexInput = await shim.fsDriver().readFile(inputFile); | ||||
| 		const promisedOutput = enexXmlToHtml(enexInput, resources); | ||||
|  | ||||
| 		promisedOutput.then(() => { | ||||
| 			// Promise should not be resolved | ||||
| 			expect(false).toEqual(true); | ||||
| 		}, (reason) => { | ||||
| 			expect(reason) | ||||
| 				.toBe('Hash with no associated resource: 89ce7da62c6b2832929a6964237e98e9'); | ||||
| 		}); | ||||
| 	})); | ||||
|  | ||||
| }); | ||||
							
								
								
									
										8
									
								
								CliClient/tests/enex_to_html/attachment.enex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								CliClient/tests/enex_to_html/attachment.enex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <en-note> | ||||
|   <div> | ||||
|     <en-media hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" /> | ||||
|   </div> | ||||
|   <div> | ||||
|     <br /> | ||||
|   </div> | ||||
| </en-note> | ||||
							
								
								
									
										7
									
								
								CliClient/tests/enex_to_html/attachment.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								CliClient/tests/enex_to_html/attachment.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <en-note> | ||||
|   <div><a href="#" hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" alt="attachment-1" onclick="ipcProxySendToHost('joplin://21ca2b948f222a38802940ec7e2e5de3'); return false;">attachment-1</a></div> | ||||
|   <div> | ||||
|     <br> | ||||
|     <br> | ||||
|   </div> | ||||
| </en-note> | ||||
							
								
								
									
										11
									
								
								CliClient/tests/enex_to_html/checklist-list.enex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								CliClient/tests/enex_to_html/checklist-list.enex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| <en-note> | ||||
|   <div> | ||||
|     <p>For example, consider an exported Evernote list with todo checkboxes like this:</p> | ||||
|  | ||||
|     <ul> | ||||
|       <li><div><en-todo checked="true"/>Foo</div></li> | ||||
|       <li><div><en-todo checked="false"/><b>Bar</b></div></li> | ||||
|       <li><div><en-todo checked="false"/><i>Baz</i></div></li> | ||||
|     </ul> | ||||
|   </div> | ||||
| </en-note> | ||||
							
								
								
									
										16
									
								
								CliClient/tests/enex_to_html/checklist-list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								CliClient/tests/enex_to_html/checklist-list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <en-note> | ||||
|   <div> | ||||
|     <p>For example, consider an exported Evernote list with todo checkboxes like this:</p> | ||||
|     <ul> | ||||
|       <li> | ||||
|         <div><input type="checkbox" onclick="return false;">Foo</div> | ||||
|       </li> | ||||
|       <li> | ||||
|         <div><input type="checkbox" onclick="return false;"><b>Bar</b></div> | ||||
|       </li> | ||||
|       <li> | ||||
|         <div><input type="checkbox" onclick="return false;"><i>Baz</i></div> | ||||
|       </li> | ||||
|     </ul> | ||||
|   </div> | ||||
| </en-note> | ||||
							
								
								
									
										3
									
								
								CliClient/tests/enex_to_html/en-media--audio.enex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								CliClient/tests/enex_to_html/en-media--audio.enex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"> | ||||
| <en-note><div><en-media hash="9168ee833d03c5ea7c730ac6673978c1" type="audio/x-m4a" title="Attachment"/></div><div><br/></div></en-note> | ||||
							
								
								
									
										13
									
								
								CliClient/tests/enex_to_html/en-media--audio.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								CliClient/tests/enex_to_html/en-media--audio.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <en-note> | ||||
|   <div> | ||||
|     <audio controls="" preload="none" style="width:480px;"> | ||||
|       <source src=":/9168ee833d03c5ea7c730ac6673978c1" type="audio/mp4"> | ||||
|       <p>Your browser does not support HTML5 audio.</p> | ||||
|     </audio> | ||||
|     <p><a href=":/9168ee833d03c5ea7c730ac6673978c1" onclick="ipcProxySendToHost('joplin://9168ee833d03c5ea7c730ac6673978c1'); return false;">audio test</a></p> | ||||
|   </div> | ||||
|   <div> | ||||
|     <br> | ||||
|     <br> | ||||
|   </div> | ||||
| </en-note> | ||||
							
								
								
									
										1
									
								
								CliClient/tests/enex_to_html/en-media--image.enex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								CliClient/tests/enex_to_html/en-media--image.enex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"><en-note><div><en-todo checked="false" />This is a test</div><div><en-todo checked="false" />A test for <span style="font-weight: bold;">bold</span></div><div><en-todo checked="false" />A test for <i>italic</i><br /></div><div><br /></div><div><i><en-media hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" /></i></div></en-note> | ||||
							
								
								
									
										14
									
								
								CliClient/tests/enex_to_html/en-media--image.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								CliClient/tests/enex_to_html/en-media--image.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <en-note> | ||||
|   <div><input type="checkbox" onclick="return false;">This is a test</div> | ||||
|   <div><input type="checkbox" onclick="return false;">A test for <span style="font-weight: bold;">bold</span></div> | ||||
|   <div> | ||||
|     <input type="checkbox" onclick="return false;">A test for <i>italic</i> | ||||
|     <br> | ||||
|     <br> | ||||
|   </div> | ||||
|   <div> | ||||
|     <br> | ||||
|     <br> | ||||
|   </div> | ||||
|   <div><i><img src=":/89ce7da62c6b2832929a6964237e98e9" hash="89ce7da62c6b2832929a6964237e98e9" type="image/jpeg" alt=""></i></div> | ||||
| </en-note> | ||||
							
								
								
									
										5
									
								
								CliClient/tests/enex_to_html/svg.enex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								CliClient/tests/enex_to_html/svg.enex
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <en-note> | ||||
|   <div> | ||||
|     <img style="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" src="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"/> | ||||
|   </div> | ||||
| </en-note> | ||||
							
								
								
									
										3
									
								
								CliClient/tests/enex_to_html/svg.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								CliClient/tests/enex_to_html/svg.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| <en-note> | ||||
|   <div><img style="margin:0px;padding:0px;outline:0px;width:74px;height:36px;position:absolute;bottom:-5px;left:0px;transform:translate(0px, 100%);stroke-dasharray:90;transition:stroke-dashoffset 0.5s cubic-bezier(0.97, 0.16, 0.62, 0.76) 0s;stroke-dashoffset:0;" src="data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' data-evernote-id='97' class='js-evernote-checked'%3e%3cuse xlink:href='https://wordminds.com/wp-content/themes/wordminds/assets/img/hint_left.svg%23hint_left' data-evernote-id='98' class='js-evernote-checked'%3e%3c/use%3e%3c/svg%3e"></div> | ||||
| </en-note> | ||||
| @@ -378,12 +378,15 @@ class Application extends BaseApplication { | ||||
| 								message: _('Importing from "%s" as "%s" format. Please wait...', path, module.format), | ||||
| 							}); | ||||
|  | ||||
| 							const importOptions = {}; | ||||
| 							importOptions.path = path; | ||||
| 							importOptions.format = module.format; | ||||
| 							importOptions.destinationFolderId = !module.isNoteArchive && moduleSource === 'file' ? selectedFolderId : null; | ||||
| 							importOptions.onError = (error) => { | ||||
| 								console.warn(error); | ||||
| 							const importOptions = { | ||||
| 								path, | ||||
| 								format: module.format, | ||||
| 								modulePath: module.path, | ||||
| 								onError: console.warn, | ||||
| 								destinationFolderId: | ||||
| 								!module.isNoteArchive && moduleSource === 'file' | ||||
| 									? selectedFolderId | ||||
| 									: null, | ||||
| 							}; | ||||
|  | ||||
| 							const service = new InteropService(); | ||||
|   | ||||
							
								
								
									
										1281
									
								
								ElectronClient/app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1281
									
								
								ElectronClient/app/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -87,6 +87,7 @@ | ||||
|     "async-mutex": "^0.1.3", | ||||
|     "base-64": "^0.1.0", | ||||
|     "chokidar": "^3.0.0", | ||||
|     "clean-html": "^1.5.0", | ||||
|     "compare-versions": "^3.2.1", | ||||
|     "diacritics": "^1.3.0", | ||||
|     "diff-match-patch": "^1.0.4", | ||||
|   | ||||
							
								
								
									
										170
									
								
								ReactNativeClient/lib/import-enex-html-gen.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								ReactNativeClient/lib/import-enex-html-gen.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| const stringToStream = require('string-to-stream'); | ||||
| const cleanHtml = require('clean-html'); | ||||
| const resourceUtils = require('lib/resourceUtils.js'); | ||||
|  | ||||
| function addResourceTag(lines, resource, attributes) { | ||||
| 	// Note: refactor to use Resource.markdownTag | ||||
| 	if (!attributes.alt) attributes.alt = resource.title; | ||||
| 	if (!attributes.alt) attributes.alt = resource.filename; | ||||
| 	if (!attributes.alt) attributes.alt = ''; | ||||
|  | ||||
| 	const src = `:/${resource.id}`; | ||||
|  | ||||
| 	if (resourceUtils.isImageMimeType(resource.mime)) { | ||||
| 		lines.push(resourceUtils.imgElement({src, attributes})); | ||||
| 	} else if (resource.mime === 'audio/x-m4a') { | ||||
| 		/** | ||||
| 		 * TODO: once https://github.com/laurent22/joplin/issues/1794 is resolved, | ||||
| 		 * come back to this and make sure it works. | ||||
| 		 */ | ||||
| 		lines.push(resourceUtils.audioElement({ | ||||
| 			src, | ||||
| 			alt: attributes.alt, | ||||
| 			id: resource.id, | ||||
| 		})); | ||||
| 	} else { | ||||
| 		// TODO: figure out what other mime types can be handled more gracefully | ||||
| 		lines.push(resourceUtils.attachmentElement({ | ||||
| 			src, | ||||
| 			attributes, | ||||
| 			id: resource.id, | ||||
| 		})); | ||||
| 	} | ||||
|  | ||||
| 	return lines; | ||||
| } | ||||
|  | ||||
| function attributeToLowerCase(node) { | ||||
| 	if (!node.attributes) return {}; | ||||
| 	let output = {}; | ||||
| 	for (let n in node.attributes) { | ||||
| 		if (!node.attributes.hasOwnProperty(n)) continue; | ||||
| 		output[n.toLowerCase()] = node.attributes[n]; | ||||
| 	} | ||||
| 	return output; | ||||
| } | ||||
|  | ||||
| function enexXmlToHtml_(stream, resources) { | ||||
| 	let remainingResources = resources.slice(); | ||||
|  | ||||
| 	const removeRemainingResource = id => { | ||||
| 		for (let i = 0; i < remainingResources.length; i++) { | ||||
| 			const r = remainingResources[i]; | ||||
| 			if (r.id === id) { | ||||
| 				remainingResources.splice(i, 1); | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		const options = {}; | ||||
| 		const strict = false; | ||||
| 		var saxStream = require('sax').createStream(strict, options); | ||||
|  | ||||
| 		let section = { | ||||
| 			type: 'text', | ||||
| 			lines: [], | ||||
| 			parent: null, | ||||
| 		}; | ||||
|  | ||||
| 		saxStream.on('error', function(e) { | ||||
| 			console.warn(e); | ||||
| 			// reject(e); | ||||
| 		}); | ||||
|  | ||||
|  | ||||
| 		saxStream.on('text', function(text) { | ||||
| 			section.lines.push(text); | ||||
| 		}); | ||||
|  | ||||
| 		saxStream.on('opentag', function(node) { | ||||
| 			const tagName = node.name.toLowerCase(); | ||||
| 			const attributesStr = resourceUtils.attributesToStr(node.attributes); | ||||
|  | ||||
| 			if (tagName === 'en-media') { | ||||
| 				const nodeAttributes = attributeToLowerCase(node); | ||||
| 				const hash = nodeAttributes.hash; | ||||
|  | ||||
| 				let resource = null; | ||||
| 				for (let i = 0; i < resources.length; i++) { | ||||
| 					let r = resources[i]; | ||||
| 					if (r.id == hash) { | ||||
| 						resource = r; | ||||
| 						removeRemainingResource(r.id); | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if (!resource) { | ||||
| 					// TODO: Extract this duplicate of code in ./import-enex-md-gen.js | ||||
| 					let found = false; | ||||
| 					for (let i = 0; i < remainingResources.length; i++) { | ||||
| 						let r = remainingResources[i]; | ||||
| 						if (!r.id) { | ||||
| 							resource = Object.assign({}, r); | ||||
| 							resource.id = hash; | ||||
| 							remainingResources.splice(i, 1); | ||||
| 							found = true; | ||||
| 							break; | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					if (!found) { | ||||
| 						reject(`Hash with no associated resource: ${hash}`); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// If the resource does not appear among the note's resources, it | ||||
| 				// means it's an attachement. It will be appended along with the | ||||
| 				// other remaining resources at the bottom of the markdown text. | ||||
| 				if (resource && !!resource.id) { | ||||
| 					section.lines = addResourceTag(section.lines, resource, nodeAttributes); | ||||
| 				} | ||||
| 			} else if (tagName == 'en-todo') { | ||||
| 				section.lines.push('<input type="checkbox" onclick="return false;" />'); | ||||
| 			} else if (node.isSelfClosing) { | ||||
| 				section.lines.push(`<${tagName}${attributesStr}>`); | ||||
| 			} else { | ||||
| 				section.lines.push(`<${tagName}${attributesStr} />`); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		saxStream.on('closetag', function(n) { | ||||
| 			const tagName = n ? n.toLowerCase() : n; | ||||
| 			section.lines.push(`</${tagName}>`); | ||||
| 		}); | ||||
|  | ||||
| 		saxStream.on('attribute', function() {}); | ||||
|  | ||||
| 		saxStream.on('end', function() { | ||||
| 			resolve({ | ||||
| 				content: section, | ||||
| 				resources: remainingResources, | ||||
| 			}); | ||||
| 		}); | ||||
|  | ||||
| 		stream.pipe(saxStream); | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| async function enexXmlToHtml(xmlString, resources, options = {}) { | ||||
| 	const stream = stringToStream(xmlString); | ||||
| 	let result = await enexXmlToHtml_(stream, resources, options); | ||||
|  | ||||
| 	try { | ||||
| 		const preCleaning = result.content.lines.join(''); // xmlString | ||||
| 		const final = await beautifyHtml(preCleaning); | ||||
| 		return final.join(''); | ||||
| 	} catch (error) { | ||||
| 		console.warn(error); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const beautifyHtml = (html) => { | ||||
| 	return new Promise((resolve) => { | ||||
| 		const options = {wrap: 0}; | ||||
| 		cleanHtml.clean(html, options, (...cleanedHtml) => resolve(cleanedHtml)); | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| module.exports = {enexXmlToHtml}; | ||||
| @@ -1,5 +1,6 @@ | ||||
| const stringPadding = require('string-padding'); | ||||
| const stringToStream = require('string-to-stream'); | ||||
| const resourceUtils = require('lib/resourceUtils.js'); | ||||
|  | ||||
| const BLOCK_OPEN = '[[BLOCK_OPEN]]'; | ||||
| const BLOCK_CLOSE = '[[BLOCK_CLOSE]]'; | ||||
| @@ -295,12 +296,6 @@ function collapseWhiteSpaceAndAppend(lines, state, text) { | ||||
| 	return lines; | ||||
| } | ||||
|  | ||||
| const imageMimeTypes = ['image/cgm', 'image/fits', 'image/g3fax', 'image/gif', 'image/ief', 'image/jp2', 'image/jpeg', 'image/jpm', 'image/jpx', 'image/naplps', 'image/png', 'image/prs.btif', 'image/prs.pti', 'image/t38', 'image/tiff', 'image/tiff-fx', 'image/vnd.adobe.photoshop', 'image/vnd.cns.inf2', 'image/vnd.djvu', 'image/vnd.dwg', 'image/vnd.dxf', 'image/vnd.fastbidsheet', 'image/vnd.fpx', 'image/vnd.fst', 'image/vnd.fujixerox.edmics-mmr', 'image/vnd.fujixerox.edmics-rlc', 'image/vnd.globalgraphics.pgb', 'image/vnd.microsoft.icon', 'image/vnd.mix', 'image/vnd.ms-modi', 'image/vnd.net-fpx', 'image/vnd.sealed.png', 'image/vnd.sealedmedia.softseal.gif', 'image/vnd.sealedmedia.softseal.jpg', 'image/vnd.svf', 'image/vnd.wap.wbmp', 'image/vnd.xiff']; | ||||
|  | ||||
| function isImageMimeType(m) { | ||||
| 	return imageMimeTypes.indexOf(m) >= 0; | ||||
| } | ||||
|  | ||||
| function tagAttributeToMdText(attr) { | ||||
| 	// HTML attributes may contain newlines so remove them. | ||||
| 	// https://github.com/laurent22/joplin/issues/1583 | ||||
| @@ -318,7 +313,7 @@ function addResourceTag(lines, resource, alt = '') { | ||||
| 	if (!alt) alt = ''; | ||||
|  | ||||
| 	alt = tagAttributeToMdText(alt); | ||||
| 	if (isImageMimeType(resource.mime)) { | ||||
| 	if (resourceUtils.isImageMimeType(resource.mime)) { | ||||
| 		lines.push('`); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ const Note = require('lib/models/Note.js'); | ||||
| const Tag = require('lib/models/Tag.js'); | ||||
| const Resource = require('lib/models/Resource.js'); | ||||
| const { enexXmlToMd } = require('./import-enex-md-gen.js'); | ||||
| const { enexXmlToHtml } = require('./import-enex-html-gen.js'); | ||||
| const { time } = require('lib/time-utils.js'); | ||||
| const Levenshtein = require('levenshtein'); | ||||
| const md5 = require('md5'); | ||||
| @@ -164,6 +165,7 @@ async function saveNoteToStorage(note, fuzzyMatching = false) { | ||||
|  | ||||
| function importEnex(parentFolderId, filePath, importOptions = null) { | ||||
| 	if (!importOptions) importOptions = {}; | ||||
| 	// console.info(JSON.stringify({importOptions}, null, 2)); | ||||
| 	if (!('fuzzyMatching' in importOptions)) importOptions.fuzzyMatching = false; | ||||
| 	if (!('onProgress' in importOptions)) importOptions.onProgress = function() {}; | ||||
| 	if (!('onError' in importOptions)) importOptions.onError = function() {}; | ||||
| @@ -216,9 +218,15 @@ function importEnex(parentFolderId, filePath, importOptions = null) { | ||||
|  | ||||
| 				while (notes.length) { | ||||
| 					let note = notes.shift(); | ||||
| 					const body = await enexXmlToMd(note.bodyXml, note.resources); | ||||
| 					const body = importOptions.outputFormat === 'html' ? | ||||
| 						await enexXmlToHtml(note.bodyXml, note.resources) : | ||||
| 						await enexXmlToMd(note.bodyXml, note.resources); | ||||
| 					delete note.bodyXml; | ||||
|  | ||||
| 					note.markup_language = importOptions.outputFormat === 'html' ? | ||||
| 						Note.MARKUP_LANGUAGE_HTML : | ||||
| 						Note.MARKUP_LANGUAGE_MARKDOWN; | ||||
|  | ||||
| 					// console.info('*************************************************************************'); | ||||
| 					// console.info(body); | ||||
| 					// console.info('*************************************************************************'); | ||||
|   | ||||
							
								
								
									
										84
									
								
								ReactNativeClient/lib/resourceUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								ReactNativeClient/lib/resourceUtils.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| const imageMimeTypes = [ | ||||
| 	'image/cgm', | ||||
| 	'image/fits', | ||||
| 	'image/g3fax', | ||||
| 	'image/gif', | ||||
| 	'image/ief', | ||||
| 	'image/jp2', | ||||
| 	'image/jpeg', | ||||
| 	'image/jpm', | ||||
| 	'image/jpx', | ||||
| 	'image/naplps', | ||||
| 	'image/png', | ||||
| 	'image/prs.btif', | ||||
| 	'image/prs.pti', | ||||
| 	'image/t38', | ||||
| 	'image/tiff', | ||||
| 	'image/tiff-fx', | ||||
| 	'image/vnd.adobe.photoshop', | ||||
| 	'image/vnd.cns.inf2', | ||||
| 	'image/vnd.djvu', | ||||
| 	'image/vnd.dwg', | ||||
| 	'image/vnd.dxf', | ||||
| 	'image/vnd.fastbidsheet', | ||||
| 	'image/vnd.fpx', | ||||
| 	'image/vnd.fst', | ||||
| 	'image/vnd.fujixerox.edmics-mmr', | ||||
| 	'image/vnd.fujixerox.edmics-rlc', | ||||
| 	'image/vnd.globalgraphics.pgb', | ||||
| 	'image/vnd.microsoft.icon', | ||||
| 	'image/vnd.mix', | ||||
| 	'image/vnd.ms-modi', | ||||
| 	'image/vnd.net-fpx', | ||||
| 	'image/vnd.sealed.png', | ||||
| 	'image/vnd.sealedmedia.softseal.gif', | ||||
| 	'image/vnd.sealedmedia.softseal.jpg', | ||||
| 	'image/vnd.svf', | ||||
| 	'image/vnd.wap.wbmp', | ||||
| 	'image/vnd.xiff', | ||||
| ]; | ||||
|  | ||||
| const escapeQuotes = (str) => str.replace(/"/g, '"'); | ||||
|  | ||||
| const attributesToStr = (attributes) => | ||||
| 	Object.entries(attributes) | ||||
| 		.map(([key, value]) => ` ${key}="${escapeQuotes(value)}"`) | ||||
| 		.join(''); | ||||
|  | ||||
| const ipcProxySendToHost = (id) => | ||||
| 	`onclick="ipcProxySendToHost('joplin://${id}'); return false;"`; | ||||
|  | ||||
| const attachmentElement = ({src, attributes, id}) => | ||||
| 	[ | ||||
| 		`<a href='#' ${attributesToStr(attributes)} ${ipcProxySendToHost(id)}>`, | ||||
| 		`  ${attributes.alt || src}`, | ||||
| 		'</a>', | ||||
| 	].join(''); | ||||
|  | ||||
| const imgElement = ({src, attributes}) => | ||||
| 	`<img src="${src}" ${attributesToStr(attributes)} />`; | ||||
|  | ||||
| const audioElement = ({src, alt, id}) => | ||||
| 	[ | ||||
| 		'<audio controls preload="none" style="width:480px;">', | ||||
| 		`	<source src="${src}" type="audio/mp4" />`, | ||||
| 		'	<p>', | ||||
| 		'		Your browser does not support HTML5 audio.', | ||||
| 		'	</p>', | ||||
| 		'</audio>', | ||||
| 		'<p>', | ||||
| 		`  <a href="${src}" ${ipcProxySendToHost(id)}>`, | ||||
| 		`    ${alt || src || id || 'Download audio'}`, | ||||
| 		'  </a>', | ||||
| 		'</p>', | ||||
| 	].join(''); | ||||
|  | ||||
| const resourceUtils = { | ||||
| 	imgElement, | ||||
| 	audioElement, | ||||
| 	attachmentElement, | ||||
| 	attributesToStr, | ||||
| 	isImageMimeType: (m) => imageMimeTypes.indexOf(m) >= 0, | ||||
| }; | ||||
|  | ||||
| module.exports = resourceUtils; | ||||
| @@ -13,8 +13,7 @@ const { toTitleCase } = require('lib/string-utils'); | ||||
|  | ||||
| class InteropService { | ||||
| 	constructor() { | ||||
| 		this.modules_ = null; | ||||
| 	} | ||||
| 		this.modules_ = null;	} | ||||
|  | ||||
| 	modules() { | ||||
| 		if (this.modules_) return this.modules_; | ||||
| @@ -42,7 +41,17 @@ class InteropService { | ||||
| 				format: 'enex', | ||||
| 				fileExtensions: ['enex'], | ||||
| 				sources: ['file'], | ||||
| 				description: _('Evernote Export File'), | ||||
| 				description: _('Evernote Export File (as Markdown)'), | ||||
| 				importerClass: 'InteropService_Importer_EnexToMd', | ||||
| 				isDefault: true, | ||||
| 			}, | ||||
| 			{ | ||||
| 				format: 'enex', | ||||
| 				fileExtensions: ['enex'], | ||||
| 				sources: ['file'], | ||||
| 				description: _('Evernote Export File (as HTML)'), | ||||
| 				// TODO: Consider doing this the same way as the multiple `md` importers are handled | ||||
| 				importerClass: 'InteropService_Importer_EnexToHtml', | ||||
| 			}, | ||||
| 		]; | ||||
|  | ||||
| @@ -112,21 +121,57 @@ class InteropService { | ||||
| 		return this.modules_; | ||||
| 	} | ||||
|  | ||||
| 	moduleByFormat_(type, format) { | ||||
| 	// Find the module that matches the given type ("importer" or "exporter") | ||||
| 	// and the given format. Some formats can have multiple assocated importers | ||||
| 	// or exporters, such as ENEX. In this case, the one marked as "isDefault" | ||||
| 	// is returned. This is useful to auto-detect the module based on the format. | ||||
| 	// For more precise matching, newModuleFromPath_ should be used. | ||||
| 	findModuleByFormat_(type, format) { | ||||
| 		const modules = this.modules(); | ||||
| 		const matches = []; | ||||
| 		for (let i = 0; i < modules.length; i++) { | ||||
| 			const m = modules[i]; | ||||
| 			if (m.format === format && m.type === type) return modules[i]; | ||||
| 			if (m.format === format && m.type === type) matches.push(modules[i]); | ||||
| 		} | ||||
| 		return null; | ||||
|  | ||||
| 		const output = matches.find(m => !!m.isDefault); | ||||
| 		if (output) return output; | ||||
|  | ||||
| 		return matches.length ? matches[0] : null; | ||||
| 	} | ||||
|  | ||||
| 	newModule_(type, format) { | ||||
| 		const module = this.moduleByFormat_(type, format); | ||||
| 		if (!module) throw new Error(_('Cannot load "%s" module for format "%s"', type, format)); | ||||
| 		const ModuleClass = require(module.path); | ||||
| 	/** | ||||
| 	 * NOTE TO FUTURE SELF: It might make sense to simply move all the existing | ||||
| 	 * formatters to the `newModuleFromPath_` approach, so that there's only one way | ||||
| 	 * to do this mapping. This isn't a priority right now (per the convo in: | ||||
| 	 * https://github.com/laurent22/joplin/pull/1795#discussion_r322379121) but | ||||
| 	 * we can do it if it ever becomes necessary. | ||||
| 	 */ | ||||
| 	newModuleByFormat_(type, format) { | ||||
| 		const moduleMetadata = this.findModuleByFormat_(type, format); | ||||
| 		if (!moduleMetadata) throw new Error(_('Cannot load "%s" module for format "%s"', type, format)); | ||||
| 		const ModuleClass = require(moduleMetadata.path); | ||||
| 		const output = new ModuleClass(); | ||||
| 		output.setMetadata(module); | ||||
| 		output.setMetadata(moduleMetadata); | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * The existing `newModuleByFormat_` fn would load by the input format. This | ||||
| 	 * was fine when there was a 1-1 mapping of input formats to output formats, | ||||
| 	 * but now that we have 2 possible outputs for an `enex` input, we need to be | ||||
| 	 * explicit with which importer we want to use. | ||||
| 	 * | ||||
| 	 * https://github.com/laurent22/joplin/pull/1795#pullrequestreview-281574417 | ||||
| 	 */ | ||||
| 	newModuleFromPath_(type, options) { | ||||
| 		if (!options || !options.modulePath) { | ||||
| 			throw new Error('Cannot load module without a defined path to load from.'); | ||||
| 		} | ||||
| 		const ModuleClass = require(options.modulePath); | ||||
| 		const output = new ModuleClass(); | ||||
| 		const moduleMetadata = this.findModuleByFormat_(type, options.format); | ||||
| 		output.setMetadata({options, ...moduleMetadata}); // TODO: Check that this metadata is equivalent to module above | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| @@ -173,7 +218,17 @@ class InteropService { | ||||
|  | ||||
| 		let result = { warnings: [] }; | ||||
|  | ||||
| 		const importer = this.newModule_('importer', options.format); | ||||
| 		// console.log('options passed to InteropService:'); | ||||
| 		// console.log(JSON.stringify({options}, null, 2)); | ||||
|  | ||||
| 		let importer = null; | ||||
|  | ||||
| 		if (options.modulePath) { | ||||
| 			importer = this.newModuleFromPath_('importer', options); | ||||
| 		} else { | ||||
| 			importer = this.newModuleByFormat_('importer', options.format); | ||||
| 		} | ||||
|  | ||||
| 		await importer.init(options.path, options); | ||||
| 		result = await importer.exec(result); | ||||
|  | ||||
| @@ -248,7 +303,7 @@ class InteropService { | ||||
| 			await queueExportItem(BaseModel.TYPE_TAG, exportedTagIds[i]); | ||||
| 		} | ||||
|  | ||||
| 		const exporter = this.newModule_('exporter', exportFormat); | ||||
| 		const exporter = this.newModuleByFormat_('exporter', exportFormat); | ||||
| 		await exporter.init(exportPath); | ||||
|  | ||||
| 		const typeOrder = [BaseModel.TYPE_FOLDER, BaseModel.TYPE_RESOURCE, BaseModel.TYPE_NOTE, BaseModel.TYPE_TAG, BaseModel.TYPE_NOTE_TAG]; | ||||
|   | ||||
| @@ -0,0 +1,22 @@ | ||||
| const InteropService_Importer_Base = require('lib/services/InteropService_Importer_Base'); | ||||
| const Folder = require('lib/models/Folder.js'); | ||||
| const { filename } = require('lib/path-utils.js'); | ||||
|  | ||||
| class InteropService_Importer_EnexToHtml extends InteropService_Importer_Base { | ||||
| 	async exec(result) { | ||||
| 		const { importEnex } = require('lib/import-enex'); | ||||
|  | ||||
| 		let folder = this.options_.destinationFolder; | ||||
|  | ||||
| 		if (!folder) { | ||||
| 			const folderTitle = await Folder.findUniqueItemTitle(filename(this.sourcePath_)); | ||||
| 			folder = await Folder.save({ title: folderTitle }); | ||||
| 		} | ||||
|  | ||||
| 		await importEnex(folder.id, this.sourcePath_, {...this.options_, outputFormat: 'html'}); | ||||
|  | ||||
| 		return result; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| module.exports = InteropService_Importer_EnexToHtml; | ||||
| @@ -2,7 +2,7 @@ const InteropService_Importer_Base = require('lib/services/InteropService_Import | ||||
| const Folder = require('lib/models/Folder.js'); | ||||
| const { filename } = require('lib/path-utils.js'); | ||||
| 
 | ||||
| class InteropService_Importer_Enex extends InteropService_Importer_Base { | ||||
| class InteropService_Importer_EnexToMd extends InteropService_Importer_Base { | ||||
| 	async exec(result) { | ||||
| 		const { importEnex } = require('lib/import-enex'); | ||||
| 
 | ||||
| @@ -19,4 +19,4 @@ class InteropService_Importer_Enex extends InteropService_Importer_Base { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| module.exports = InteropService_Importer_Enex; | ||||
| module.exports = InteropService_Importer_EnexToMd; | ||||
		Reference in New Issue
	
	Block a user