1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Revert "Desktop: Add ENEX to HTML export (#1795)"

This reverts commit 2f14832c34.

Reverting PR #1795 due to broken MD import and other issues
This commit is contained in:
Laurent Cozic 2019-09-20 22:18:09 +01:00
parent c7c57ab2a5
commit 50b66cceca
24 changed files with 678 additions and 1265 deletions

3
.gitignore vendored
View File

@ -42,5 +42,4 @@ ReactNativeClient/lib/csstojs/
ReactNativeClient/lib/rnInjectedJs/
ElectronClient/app/gui/note-viewer/fonts/
ElectronClient/app/gui/note-viewer/lib.js
Tools/commit_hook.txt
.vscode/*
Tools/commit_hook.txt

View File

@ -312,22 +312,6 @@
"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",
@ -566,32 +550,6 @@
"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",
@ -600,23 +558,6 @@
"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",
@ -1074,41 +1015,22 @@
"uglify-js": "3.3.x"
}
},
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
"http-errors": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
"requires": {
"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"
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
},
"dependencies": {
"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"
}
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}
}
},
@ -3208,7 +3130,7 @@
"requires": {
"chalk": "^2.1.0",
"emphasize": "^1.5.0",
"node-emoji": "git+https://github.com/laurent22/node-emoji.git#9fa01eac463e94dde1316ef8c53089eeef4973b5",
"node-emoji": "git+https://github.com/laurent22/node-emoji.git",
"slice-ansi": "^1.0.0",
"string-width": "^2.1.1",
"terminal-kit": "^1.13.11",

View File

@ -31,7 +31,6 @@
"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",

View File

@ -1,108 +0,0 @@
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');
});
}));
});

View File

@ -1,8 +0,0 @@
<en-note>
<div>
<en-media hash="21ca2b948f222a38802940ec7e2e5de3" type="application/pdf" style="cursor:pointer;" />
</div>
<div>
<br />
</div>
</en-note>

View File

@ -1,7 +0,0 @@
<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>

View File

@ -1,11 +0,0 @@
<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>

View File

@ -1,16 +0,0 @@
<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>

View File

@ -1,3 +0,0 @@
<?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>

View File

@ -1,13 +0,0 @@
<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>

View File

@ -1 +0,0 @@
<!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>

View File

@ -1,14 +0,0 @@
<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>

View File

@ -1,5 +0,0 @@
<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>

View File

@ -1,3 +0,0 @@
<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>

View File

@ -378,15 +378,12 @@ class Application extends BaseApplication {
message: _('Importing from "%s" as "%s" format. Please wait...', path, module.format),
});
const importOptions = {
path,
format: module.format,
modulePath: module.path,
onError: console.warn,
destinationFolderId:
!module.isNoteArchive && moduleSource === 'file'
? selectedFolderId
: null,
const importOptions = {};
importOptions.path = path;
importOptions.format = module.format;
importOptions.destinationFolderId = !module.isNoteArchive && moduleSource === 'file' ? selectedFolderId : null;
importOptions.onError = (error) => {
console.warn(error);
};
const service = new InteropService();

File diff suppressed because it is too large Load Diff

View File

@ -87,7 +87,6 @@
"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",

View File

@ -1,170 +0,0 @@
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};

View File

@ -1,6 +1,5 @@
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]]';
@ -296,6 +295,12 @@ 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
@ -313,7 +318,7 @@ function addResourceTag(lines, resource, alt = '') {
if (!alt) alt = '';
alt = tagAttributeToMdText(alt);
if (resourceUtils.isImageMimeType(resource.mime)) {
if (isImageMimeType(resource.mime)) {
lines.push('![');
lines.push(alt);
lines.push(`](:/${resource.id})`);

View File

@ -5,7 +5,6 @@ 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');
@ -165,7 +164,6 @@ 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() {};
@ -218,15 +216,9 @@ function importEnex(parentFolderId, filePath, importOptions = null) {
while (notes.length) {
let note = notes.shift();
const body = importOptions.outputFormat === 'html' ?
await enexXmlToHtml(note.bodyXml, note.resources) :
await enexXmlToMd(note.bodyXml, note.resources);
const body = 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('*************************************************************************');

View File

@ -1,84 +0,0 @@
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;

View File

@ -13,7 +13,8 @@ const { toTitleCase } = require('lib/string-utils');
class InteropService {
constructor() {
this.modules_ = null; }
this.modules_ = null;
}
modules() {
if (this.modules_) return this.modules_;
@ -41,16 +42,7 @@ class InteropService {
format: 'enex',
fileExtensions: ['enex'],
sources: ['file'],
description: _('Evernote Export File (as Markdown)'),
importerClass: 'InteropService_Importer_EnexToMd',
},
{
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',
description: _('Evernote Export File'),
},
];
@ -120,9 +112,8 @@ class InteropService {
return this.modules_;
}
findModuleByFormat_(type, format) {
moduleByFormat_(type, format) {
const modules = this.modules();
// console.log(JSON.stringify({modules}, null, 2))
for (let i = 0; i < modules.length; i++) {
const m = modules[i];
if (m.format === format && m.type === type) return modules[i];
@ -130,8 +121,8 @@ class InteropService {
return null;
}
newModuleByFormat_(type, format) {
const module = this.findModuleByFormat_(type, format);
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);
const output = new ModuleClass();
@ -139,26 +130,6 @@ class InteropService {
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.
*
* In the long run, 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.
*
* https://github.com/laurent22/joplin/pull/1795#pullrequestreview-281574417
*/
newModuleFromPath_(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();
output.setMetadata(options); // TODO: Check that this metadata is equivalent to module above
return output;
}
moduleByFileExtension_(type, ext) {
ext = ext.toLowerCase();
@ -202,10 +173,7 @@ class InteropService {
let result = { warnings: [] };
// console.log('options passed to InteropService:');
// console.log(JSON.stringify({options}, null, 2));
const importer = this.newModuleFromPath_(options);
const importer = this.newModule_('importer', options.format);
await importer.init(options.path, options);
result = await importer.exec(result);
@ -280,7 +248,7 @@ class InteropService {
await queueExportItem(BaseModel.TYPE_TAG, exportedTagIds[i]);
}
const exporter = this.newModuleByFormat_('exporter', exportFormat);
const exporter = this.newModule_('exporter', exportFormat);
await exporter.init(exportPath);
const typeOrder = [BaseModel.TYPE_FOLDER, BaseModel.TYPE_RESOURCE, BaseModel.TYPE_NOTE, BaseModel.TYPE_TAG, BaseModel.TYPE_NOTE_TAG];

View File

@ -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_EnexToMd extends InteropService_Importer_Base {
class InteropService_Importer_Enex extends InteropService_Importer_Base {
async exec(result) {
const { importEnex } = require('lib/import-enex');
@ -19,4 +19,4 @@ class InteropService_Importer_EnexToMd extends InteropService_Importer_Base {
}
}
module.exports = InteropService_Importer_EnexToMd;
module.exports = InteropService_Importer_Enex;

View File

@ -1,22 +0,0 @@
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;