diff --git a/CliClient/tests/MdToMd.js b/CliClient/tests/MdToMd.js new file mode 100644 index 0000000000..9afe16815f --- /dev/null +++ b/CliClient/tests/MdToMd.js @@ -0,0 +1,41 @@ +const mdImporterService = require('lib/services/InteropService_Importer_Md'); +const Note = require('lib/models/Note.js'); +const { setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js'); + +const importer = new mdImporterService(); + + +describe('InteropService_Importer_Md: importLocalImages', function() { + beforeEach(async (done) => { + await setupDatabaseAndSynchronizer(1); + await switchClient(1); + done(); + }); + it('should import linked files and modify tags appropriately', async function() { + const tagNonExistentFile = '![does not exist](does_not_exist.png)'; + const note = await importer.importFile(`${__dirname}/md_to_md/sample.md`, 'notebook'); + let items = await Note.linkedItems(note.body); + expect(items.length).toBe(2); + const inexistentLinkUnchanged = note.body.includes(tagNonExistentFile); + expect(inexistentLinkUnchanged).toBe(true); + }); + it('should only create 1 resource for duplicate links, all tags should be updated', async function() { + const note = await importer.importFile(`${__dirname}/md_to_md/sample-duplicate-links.md`, 'notebook'); + let items = await Note.linkedItems(note.body); + expect(items.length).toBe(1); + const reg = new RegExp(items[0].id, 'g'); + const matched = note.body.match(reg); + expect(matched.length).toBe(2); + }); + it('should import linked files and modify tags appropriately when link is also in alt text', async function() { + const note = await importer.importFile(`${__dirname}/md_to_md/sample-link-in-alt-text.md`, 'notebook'); + let items = await Note.linkedItems(note.body); + expect(items.length).toBe(1); + }); + it('should passthrough unchanged if no links present', async function() { + const note = await importer.importFile(`${__dirname}/md_to_md/sample-no-links.md`, 'notebook'); + let items = await Note.linkedItems(note.body); + expect(items.length).toBe(0); + expect(note.body).toContain('Unidentified vessel travelling at sub warp speed, bearing 235.7. Fluctuations in energy readings from it, Captain. All transporters off.'); + }); +}); diff --git a/CliClient/tests/md_to_md/sample-duplicate-links.md b/CliClient/tests/md_to_md/sample-duplicate-links.md new file mode 100644 index 0000000000..6fd622a7fa --- /dev/null +++ b/CliClient/tests/md_to_md/sample-duplicate-links.md @@ -0,0 +1,2 @@ +![link 1](../support/photo.jpg) +![link 2](../support/photo.jpg) diff --git a/CliClient/tests/md_to_md/sample-link-in-alt-text.md b/CliClient/tests/md_to_md/sample-link-in-alt-text.md new file mode 100644 index 0000000000..b472c2e017 --- /dev/null +++ b/CliClient/tests/md_to_md/sample-link-in-alt-text.md @@ -0,0 +1,3 @@ +# Markdown +![../support/photo.jpg](../support/photo.jpg) should put resource link inside () not [] +![../support/photo.jpg]( ../support/photo.jpg ) this case (spaces before/after link but within parens) is not currently covered diff --git a/CliClient/tests/md_to_md/sample-no-links.md b/CliClient/tests/md_to_md/sample-no-links.md new file mode 100644 index 0000000000..8c5adc1227 --- /dev/null +++ b/CliClient/tests/md_to_md/sample-no-links.md @@ -0,0 +1,3 @@ +# Markdown + +Unidentified vessel travelling at sub warp speed, bearing 235.7. Fluctuations in energy readings from it, Captain. All transporters off. diff --git a/CliClient/tests/md_to_md/sample.md b/CliClient/tests/md_to_md/sample.md new file mode 100644 index 0000000000..ed9d36c029 --- /dev/null +++ b/CliClient/tests/md_to_md/sample.md @@ -0,0 +1,13 @@ +# Markdown + +lorem ipsum ![alt text here](../support/photo.jpg) +- [ ] check! +- [ ] boxes! + +![alt text here](../support/photo-two.jpg)ipsum lorem + +**strong text** +![does not exist](does_not_exist.png) lorem ipsum + +**some directory** +![a directory](../support) lorem ipsum diff --git a/CliClient/tests/support/photo-two.jpg b/CliClient/tests/support/photo-two.jpg new file mode 100644 index 0000000000..b258679de6 Binary files /dev/null and b/CliClient/tests/support/photo-two.jpg differ diff --git a/ReactNativeClient/lib/services/InteropService_Importer_Md.js b/ReactNativeClient/lib/services/InteropService_Importer_Md.js index 7975c379b9..08bdff7899 100644 --- a/ReactNativeClient/lib/services/InteropService_Importer_Md.js +++ b/ReactNativeClient/lib/services/InteropService_Importer_Md.js @@ -1,10 +1,12 @@ const InteropService_Importer_Base = require('lib/services/InteropService_Importer_Base'); const Folder = require('lib/models/Folder.js'); const Note = require('lib/models/Note.js'); -const { basename, filename, rtrimSlashes } = require('lib/path-utils.js'); +const { basename, filename, rtrimSlashes, fileExtension, dirname } = require('lib/path-utils.js'); const { shim } = require('lib/shim'); const { _ } = require('lib/locale'); -const { fileExtension } = require('lib/path-utils'); +const {extractImageUrls} = require('lib/markdownUtils'); +const {unique} = require('lib/ArrayUtils'); +const { pregQuote } = require('lib/string-utils-common'); class InteropService_Importer_Md extends InteropService_Importer_Base { async exec(result) { @@ -54,21 +56,51 @@ class InteropService_Importer_Md extends InteropService_Importer_Base { } } + /** + * Parse text for links, attempt to find local file, if found create Joplin resource + * and update link accordingly. + */ + async importLocalImages(filePath, md) { + let updated = md; + const imageLinks = unique(extractImageUrls(md)); + await Promise.all(imageLinks.map(async (link) => { + const attachmentPath = filename(`${dirname(filePath)}/${link}`, true); + const pathWithExtension = `${attachmentPath}.${fileExtension(link)}`; + const stat = await shim.fsDriver().stat(pathWithExtension); + const isDir = stat ? stat.isDirectory() : false; + if (stat && !isDir) { + const resource = await shim.createResourceFromPath(pathWithExtension); + // NOTE: use ](link) in case the link also appears elsewhere, such as in alt text + const linkPatternEscaped = pregQuote(`](${link})`); + const reg = new RegExp(linkPatternEscaped, 'g'); + updated = updated.replace(reg, `](:/${resource.id})`); + } + })); + return updated; + } + async importFile(filePath, parentFolderId) { const stat = await shim.fsDriver().stat(filePath); if (!stat) throw new Error(`Cannot read ${filePath}`); const title = filename(filePath); const body = await shim.fsDriver().readFile(filePath); + let updatedBody; + try { + updatedBody = await this.importLocalImages(filePath, body); + } catch (error) { + // console.error(`Problem importing links for file ${filePath}, error:\n ${error}`); + } const note = { parent_id: parentFolderId, title: title, - body: body, + body: updatedBody || body, updated_time: stat.mtime.getTime(), created_time: stat.birthtime.getTime(), user_updated_time: stat.mtime.getTime(), user_created_time: stat.birthtime.getTime(), }; - await Note.save(note, { autoTimestamp: false }); + + return Note.save(note, { autoTimestamp: false }); } }