Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions browser/main/NoteList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import moment from 'moment'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import ConfigManager from 'browser/main/lib/ConfigManager'
import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple'
Expand Down Expand Up @@ -662,6 +663,10 @@ class NoteList extends React.Component {
title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content
})
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
return note
})
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
Expand Down
53 changes: 47 additions & 6 deletions browser/main/lib/dataApi/attachmentManagement.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr

const targetStorage = findStorage.findStorage(storageKey)

const inputFile = fs.createReadStream(sourceFilePath)
const inputFileStream = fs.createReadStream(sourceFilePath)
let destinationName
if (useRandomName) {
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
Expand All @@ -52,8 +52,10 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
createAttachmentDestinationFolder(targetStorage.path, noteKey)
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
inputFile.pipe(outputFile)
resolve(destinationName)
inputFileStream.pipe(outputFile)
inputFileStream.on('end', () => {
resolve(destinationName)
})
} catch (e) {
return reject(e)
}
Expand Down Expand Up @@ -149,7 +151,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
fs.writeFile(imagePath, binaryData, 'binary')
fs.writeFileSync(imagePath, binaryData, 'binary')
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
codeEditor.insertAttachmentMd(imageMd)
}
Expand All @@ -174,7 +176,7 @@ function getAttachmentsInContent (markdownContent) {
* @returns {String[]} Absolute paths of the referenced attachments
*/
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
const temp = getAttachmentsInContent(markdownContent)
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)))
Expand All @@ -198,8 +200,19 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
if (fse.existsSync(src)) {
fse.moveSync(src, dest)
}
return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey)
}

/**
* Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one
* @param noteContent content that should be modified
* @param oldNoteKey note key to be replaced
* @param newNoteKey note key serving as a replacement
* @returns {String} modified note content
*/
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
if (noteContent) {
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
}
return noteContent
}
Expand Down Expand Up @@ -268,6 +281,33 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
}
}

/**
* Clones the attachments of a given note.
* Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
* @param oldNote Note that is being cloned
* @param newNote Clone of the note
*/
function cloneAttachments (oldNote, newNote) {
if (newNote.type === 'MARKDOWN_NOTE') {
const oldStorage = findStorage.findStorage(oldNote.storage)
const newStorage = findStorage.findStorage(newNote.storage)
const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || []

const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key)
if (!sander.existsSync(destinationFolder)) {
sander.mkdirSync(destinationFolder)
}

for (const attachment of attachmentsPaths) {
const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
sander.copyFileSync(attachment).to(destination)
}
newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
} else {
console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs')
}
}

module.exports = {
copyAttachment,
fixLocalURLS,
Expand All @@ -280,6 +320,7 @@ module.exports = {
deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote,
moveAttachments,
cloneAttachments,
STORAGE_FOLDER_PLACEHOLDER,
DESTINATION_FOLDER
}
88 changes: 86 additions & 2 deletions tests/dataApi/attachmentManagement.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jest.mock('unique-slug')
const uniqueSlug = require('unique-slug')
const mdurl = require('mdurl')
const fse = require('fs-extra')
jest.mock('sander')
const sander = require('sander')

const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement')
Expand Down Expand Up @@ -50,11 +51,13 @@ it('should test that copyAttachment works correctly assuming correct working of
const noteKey = 'noteKey'
const dummyUniquePath = 'dummyPath'
const dummyStorage = {path: 'dummyStoragePath'}
const dummyReadStream = {}

dummyReadStream.pipe = jest.fn()
dummyReadStream.on = jest.fn((event, callback) => { callback() })
fs.existsSync = jest.fn()
fs.existsSync.mockReturnValue(true)
fs.createReadStream = jest.fn()
fs.createReadStream.mockReturnValue({pipe: jest.fn()})
fs.createReadStream = jest.fn(() => dummyReadStream)
fs.createWriteStream = jest.fn()

findStorage.findStorage = jest.fn()
Expand All @@ -77,7 +80,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde
const noteKey = 'noteKey'
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
const dummyReadStream = {}

dummyReadStream.pipe = jest.fn()
dummyReadStream.on = jest.fn()
fs.createReadStream = jest.fn(() => dummyReadStream)
fs.existsSync = jest.fn()
fs.existsSync.mockReturnValueOnce(true)
fs.existsSync.mockReturnValueOnce(false)
Expand All @@ -99,7 +106,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde

it('should test that copyAttachment don\'t uses a random file name if not intended ', function () {
const dummyStorage = {path: 'dummyStoragePath'}
const dummyReadStream = {}

dummyReadStream.pipe = jest.fn()
dummyReadStream.on = jest.fn()
fs.createReadStream = jest.fn(() => dummyReadStream)
fs.existsSync = jest.fn()
fs.existsSync.mockReturnValueOnce(true)
fs.existsSync.mockReturnValueOnce(false)
Expand Down Expand Up @@ -383,3 +394,76 @@ it('should test that moveAttachments returns a correct modified content version'
const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput)
expect(actualContent).toBe(expectedOutput)
})

it('should test that cloneAttachments modifies the content of the new note correctly', function () {
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
const testInput =
'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
newNote.content = testInput

const expectedOutput =
'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'pdf.pdf](pdf})'
systemUnderTest.cloneAttachments(oldNote, newNote)

expect(newNote.content).toBe(expectedOutput)
})

it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
const storagePathOld = 'storagePathOld'
const storagePathNew = 'storagePathNew'
const dummyStorageOld = {path: storagePathOld}
const dummyStorageNew = {path: storagePathNew}
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'MARKDOWN_NOTE'}
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'MARKDOWN_NOTE'}
const testInput =
'Test input' +
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' +
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
oldNote.content = testInput
newNote.content = testInput

const copyFileSyncResp = {to: jest.fn()}
sander.copyFileSync = jest.fn()
sander.copyFileSync.mockReturnValue(copyFileSyncResp)
findStorage.findStorage = jest.fn()
findStorage.findStorage.mockReturnValueOnce(dummyStorageOld)
findStorage.findStorage.mockReturnValue(dummyStorageNew)

const pathAttachmentOneFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg')
const pathAttachmentOneTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg')

const pathAttachmentTwoFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf')
const pathAttachmentTwoTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf')

systemUnderTest.cloneAttachments(oldNote, newNote)

expect(findStorage.findStorage).toHaveBeenCalledWith(oldNote.storage)
expect(findStorage.findStorage).toHaveBeenCalledWith(newNote.storage)
expect(sander.copyFileSync).toHaveBeenCalledTimes(2)
expect(copyFileSyncResp.to).toHaveBeenCalledTimes(2)
expect(sander.copyFileSync.mock.calls[0][0]).toBe(pathAttachmentOneFrom)
expect(copyFileSyncResp.to.mock.calls[0][0]).toBe(pathAttachmentOneTo)
expect(sander.copyFileSync.mock.calls[1][0]).toBe(pathAttachmentTwoFrom)
expect(copyFileSyncResp.to.mock.calls[1][0]).toBe(pathAttachmentTwoTo)
})

it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'SOMETHING_ELSE'}
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'SOMETHING_ELSE'}
const testInput = 'Test input'
oldNote.content = testInput
newNote.content = testInput

sander.copyFileSync = jest.fn()
findStorage.findStorage = jest.fn()

systemUnderTest.cloneAttachments(oldNote, newNote)

expect(findStorage.findStorage).not.toHaveBeenCalled()
expect(sander.copyFileSync).not.toHaveBeenCalled()
})