From bf3f5a5971d5705da141b69d672e41b577387eaa Mon Sep 17 00:00:00 2001 From: ehhc Date: Fri, 27 Apr 2018 08:51:33 +0200 Subject: [PATCH 1/3] Fixes #1827 -> include attachments in HTML exports of notes --- browser/components/MarkdownEditor.js | 1 + browser/components/MarkdownPreview.js | 10 +- browser/components/MarkdownSplitEditor.js | 1 + .../main/lib/dataApi/attachmentManagement.js | 40 ++++++++ browser/main/lib/dataApi/exportNote.js | 17 +--- tests/dataApi/attachmentManagement.test.js | 95 +++++++++++++++++++ 6 files changed, 147 insertions(+), 17 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 313c6f90..2c98f18e 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -294,6 +294,7 @@ class MarkdownEditor extends React.Component { onCheckboxClick={(e) => this.handleCheckboxClick(e)} showCopyNotification={config.ui.showCopyNotification} storagePath={storage.path} + noteKey={noteKey} /> ) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index aa920975..d1d5a702 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -211,8 +211,9 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber) - const body = this.markdown.render(escapeHtmlCharacters(noteContent)) + let body = this.markdown.render(escapeHtmlCharacters(noteContent)) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] + const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) files.forEach((file) => { file = file.replace('file://', '') @@ -221,6 +222,13 @@ export default class MarkdownPreview extends React.Component { dst: 'css' }) }) + attachmentsAbsolutePaths.forEach((attachment) => { + exportTasks.push({ + src: attachment, + dst: attachmentManagement.DESTINATION_FOLDER + }) + }) + body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey) let styles = '' files.forEach((file) => { diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index d82d4da3..27505a5a 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -139,6 +139,7 @@ class MarkdownSplitEditor extends React.Component { onScroll={this.handleScroll.bind(this)} showCopyNotification={config.ui.showCopyNotification} storagePath={storage.path} + noteKey={noteKey} /> ) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 6d4d7406..e696f4ff 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -3,6 +3,7 @@ const fs = require('fs') const path = require('path') const findStorage = require('browser/lib/findStorage') const mdurl = require('mdurl') +const escapeStringRegexp = require('escape-string-regexp') const STORAGE_FOLDER_PLACEHOLDER = ':storage' const DESTINATION_FOLDER = 'attachments' @@ -153,12 +154,51 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem reader.readAsDataURL(blob) } +/** + * @description Returns all attachment paths of the given markdown + * @param {String} markdownContent content in which the attachment paths should be found + * @returns {String[]} Array of the relativ paths (starting with :storage) of the attachments of the given markdown + */ +function getAttachmentsInContent (markdownContent) { + let preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) + let regexp = new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + '([a-zA-Z0-9]|-)+' + escapeStringRegexp(path.sep) + '[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?', 'g') + return preparedInput.match(regexp) +} + +/** + * @description Returns an array of the absolute paths of the attachments referenced in the given markdown code + * @param {String} markdownContent content in which the attachment paths should be found + * @param {String} storagePath path of the current storage + * @returns {String[]} Absolute paths of the referenced attachments + */ +function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) { + let temp = getAttachmentsInContent(markdownContent) + let result = [] + for (let relativePath of temp) { + result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))) + } + return result +} + +/** + * @description Deletes all :storage and noteKey references from the given input. + * @param input Input in which the references should be deleted + * @param noteKey Key of the current note + * @returns {String} Input without the references + */ +function removeStorageAndNoteReferences (input, noteKey) { + return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), DESTINATION_FOLDER) +} + module.exports = { copyAttachment, fixLocalURLS, generateAttachmentMarkdown, handleAttachmentDrop, handlePastImageEvent, + getAttachmentsInContent, + getAbsolutePathsOfAttachmentsInContent, + removeStorageAndNoteReferences, STORAGE_FOLDER_PLACEHOLDER, DESTINATION_FOLDER } diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js index 71f7d017..ffd30b8f 100755 --- a/browser/main/lib/dataApi/exportNote.js +++ b/browser/main/lib/dataApi/exportNote.js @@ -1,6 +1,5 @@ import copyFile from 'browser/main/lib/dataApi/copyFile' import { findStorage } from 'browser/lib/findStorage' -import filenamify from 'filenamify' const fs = require('fs') const path = require('path') @@ -29,21 +28,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) { throw new Error('Storage path is not found') } - let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => { - dstFilename = filenamify(dstFilename, {replacement: '_'}) - if (!path.extname(dstFilename)) { - dstFilename += path.extname(srcFilename) - } - - const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename) - - exportTasks.push({ - src: path.join(IMAGES_FOLDER_NAME, srcFilename), - dst: dstRelativePath - }) - - return `![${dstFilename}](${dstRelativePath})` - }) + let exportedData = noteContent if (outputFormatter) { exportedData = outputFormatter(exportedData, exportTasks) diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index c90e1961..78f77405 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -166,3 +166,98 @@ it('should test that generateAttachmentMarkdown works correct both with previews actual = systemUnderTest.generateAttachmentMarkdown(fileName, path, false) expect(actual).toEqual(expected) }) + +it('should test that getAttachmentsInContent finds all attachments', function () { + let testInput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

Headline

\n' + + '

\n' + + ' dummyImage.png\n' + + '

\n' + + '

\n' + + ' dummyPDF.pdf\n' + + '

\n' + + '

\n' + + ' dummyImage2.jpg\n' + + '

\n' + + ' \n' + + '' + let actual = systemUnderTest.getAttachmentsInContent(testInput) + let expected = [':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.6r4zdgc22xp', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.q2i4iw0fyx', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\d6c5ee92.jpg'] + expect(actual).toEqual(expect.arrayContaining(expected)) +}) + +it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () { + let dummyStoragePath = 'dummyStoragePath' + let testInput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

Headline

\n' + + '

\n' + + ' dummyImage.png\n' + + '

\n' + + '

\n' + + ' dummyPDF.pdf\n' + + '

\n' + + '

\n' + + ' dummyImage2.jpg\n' + + '

\n' + + ' \n' + + '' + let actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath) + let expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', + dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', + dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] + expect(actual).toEqual(expect.arrayContaining(expected)) +}) + +it('should remove the all ":storage" and noteKey references', function () { + let storageFolder = systemUnderTest.DESTINATION_FOLDER + let noteKey = 'noteKey' + let testInput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

Headline

\n' + + '

\n' + + ' dummyImage.png\n' + + '

\n' + + '

\n' + + ' dummyPDF.pdf\n' + + '

\n' + + '

\n' + + ' dummyImage2.jpg\n' + + '

\n' + + ' \n' + + '' + let storagePath = '<>' + let expectedOutput = + '\n' + + ' \n' + + ' //header\n' + + ' \n' + + ' \n' + + '

Headline

\n' + + '

\n' + + ' dummyImage.png\n' + + '

\n' + + '

\n' + + ' dummyPDF.pdf\n' + + '

\n' + + '

\n' + + ' dummyImage2.jpg\n' + + '

\n' + + ' \n' + + '' + let actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) + expect(actual).toEqual(expectedOutput) +}) From 99e706bcd2276af00d62f6b641c4508079bd4bd6 Mon Sep 17 00:00:00 2001 From: ehhc Date: Fri, 27 Apr 2018 09:05:47 +0200 Subject: [PATCH 2/3] Enable yarn on travis + fix broken test --- .eslintrc | 3 +++ .travis.yml | 1 + .../TagListItem.snapshot.test.js.snap | 4 +--- tests/dataApi/attachmentManagement.test.js | 14 +++++++------- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.eslintrc b/.eslintrc index a460b507..1709c9d8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,5 +19,8 @@ "FileReader": true, "localStorage": true, "fetch": true + }, + "env": { + "jest": true } } diff --git a/.travis.yml b/.travis.yml index c68d1063..ed22d242 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ node_js: - 6 script: - npm run lint && npm run test + - yarn jest - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi' after_success: - openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv diff --git a/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap index 0521ca0e..80293ab4 100644 --- a/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap +++ b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap @@ -17,9 +17,7 @@ exports[`TagListItem renders correctly 1`] = ` # Test - - + /> diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index 78f77405..e13b2a55 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -142,13 +142,13 @@ it('should replace the all ":storage" path with the actual storage path', functi ' \n' + '

Headline

\n' + '

\n' + - ' dummyImage.png\n' + + ' dummyImage.png\n' + '

\n' + '

\n' + - ' dummyPDF.pdf\n' + + ' dummyPDF.pdf\n' + '

\n' + '

\n' + - ' dummyImage2.jpg\n' + + ' dummyImage2.jpg\n' + '

\n' + ' \n' + '' @@ -187,7 +187,7 @@ it('should test that getAttachmentsInContent finds all attachments', function () ' \n' + '' let actual = systemUnderTest.getAttachmentsInContent(testInput) - let expected = [':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.6r4zdgc22xp', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\0.q2i4iw0fyx', ':storage\\9c9c4ba3-bc1e-441f-9866-c1e9a806e31c\\d6c5ee92.jpg'] + let expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] expect(actual).toEqual(expect.arrayContaining(expected)) }) @@ -248,13 +248,13 @@ it('should remove the all ":storage" and noteKey references', function () { ' \n' + '

Headline

\n' + '

\n' + - ' dummyImage.png\n' + + ' dummyImage.png\n' + '

\n' + '

\n' + - ' dummyPDF.pdf\n' + + ' dummyPDF.pdf\n' + '

\n' + '

\n' + - ' dummyImage2.jpg\n' + + ' dummyImage2.jpg\n' + '

\n' + ' \n' + '' From e9de8f42e5da38f9793742f2b36218625bc6033f Mon Sep 17 00:00:00 2001 From: ehhc Date: Fri, 27 Apr 2018 09:16:30 +0200 Subject: [PATCH 3/3] Fix eslinter --- .../main/lib/dataApi/attachmentManagement.js | 18 ++++++------- browser/main/lib/dataApi/exportNote.js | 4 --- tests/dataApi/attachmentManagement.test.js | 25 +++++++++---------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index e696f4ff..c2e7f6d6 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -105,8 +105,8 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { const fileType = file['type'] copyAttachment(filePath, storageKey, noteKey).then((fileName) => { - let showPreview = fileType.startsWith('image') - let imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) + const showPreview = fileType.startsWith('image') + const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) codeEditor.insertAttachmentMd(imageMd) }) } @@ -140,7 +140,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) createAttachmentDestinationFolder(targetStorage.path, noteKey) - let imageName = `${uniqueSlug()}.png` + const imageName = `${uniqueSlug()}.png` const imagePath = path.join(destinationDir, imageName) reader.onloadend = function () { @@ -148,7 +148,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem base64data += base64data.replace('+', ' ') const binaryData = new Buffer(base64data, 'base64').toString('binary') fs.writeFile(imagePath, binaryData, 'binary') - let imageMd = generateAttachmentMarkdown(imageName, imagePath, true) + const imageMd = generateAttachmentMarkdown(imageName, imagePath, true) codeEditor.insertAttachmentMd(imageMd) } reader.readAsDataURL(blob) @@ -160,8 +160,8 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem * @returns {String[]} Array of the relativ paths (starting with :storage) of the attachments of the given markdown */ function getAttachmentsInContent (markdownContent) { - let preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) - let regexp = new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + '([a-zA-Z0-9]|-)+' + escapeStringRegexp(path.sep) + '[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?', 'g') + const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep) + const regexp = new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + '([a-zA-Z0-9]|-)+' + escapeStringRegexp(path.sep) + '[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?', 'g') return preparedInput.match(regexp) } @@ -172,9 +172,9 @@ function getAttachmentsInContent (markdownContent) { * @returns {String[]} Absolute paths of the referenced attachments */ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) { - let temp = getAttachmentsInContent(markdownContent) - let result = [] - for (let relativePath of temp) { + const temp = getAttachmentsInContent(markdownContent) + const result = [] + for (const relativePath of temp) { result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))) } return result diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js index ffd30b8f..e4fec5f4 100755 --- a/browser/main/lib/dataApi/exportNote.js +++ b/browser/main/lib/dataApi/exportNote.js @@ -4,10 +4,6 @@ import { findStorage } from 'browser/lib/findStorage' const fs = require('fs') const path = require('path') -const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi -// TODO: ehhc: check this -> attachmentManagement -const IMAGES_FOLDER_NAME = 'images' - /** * Export note together with images * diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js index e13b2a55..d58a8eb8 100644 --- a/tests/dataApi/attachmentManagement.test.js +++ b/tests/dataApi/attachmentManagement.test.js @@ -168,7 +168,7 @@ it('should test that generateAttachmentMarkdown works correct both with previews }) it('should test that getAttachmentsInContent finds all attachments', function () { - let testInput = + const testInput = '\n' + ' \n' + ' //header\n' + @@ -186,14 +186,14 @@ it('should test that getAttachmentsInContent finds all attachments', function () '

\n' + ' \n' + '' - let actual = systemUnderTest.getAttachmentsInContent(testInput) - let expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] + const actual = systemUnderTest.getAttachmentsInContent(testInput) + const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] expect(actual).toEqual(expect.arrayContaining(expected)) }) it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () { - let dummyStoragePath = 'dummyStoragePath' - let testInput = + const dummyStoragePath = 'dummyStoragePath' + const testInput = '\n' + ' \n' + ' //header\n' + @@ -211,17 +211,17 @@ it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute '

\n' + ' \n' + '' - let actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath) - let expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', + const actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath) + const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg'] expect(actual).toEqual(expect.arrayContaining(expected)) }) it('should remove the all ":storage" and noteKey references', function () { - let storageFolder = systemUnderTest.DESTINATION_FOLDER - let noteKey = 'noteKey' - let testInput = + const storageFolder = systemUnderTest.DESTINATION_FOLDER + const noteKey = 'noteKey' + const testInput = '\n' + ' \n' + ' //header\n' + @@ -239,8 +239,7 @@ it('should remove the all ":storage" and noteKey references', function () { '

\n' + ' \n' + '' - let storagePath = '<>' - let expectedOutput = + const expectedOutput = '\n' + ' \n' + ' //header\n' + @@ -258,6 +257,6 @@ it('should remove the all ":storage" and noteKey references', function () { '

\n' + ' \n' + '' - let actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) + const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey) expect(actual).toEqual(expectedOutput) })