Skip to content

Commit 69e012a

Browse files
authored
Merge pull request #1939 from ehhc/cloning_a_note_should_clone_attachments
Cloning a note should clone attachments
2 parents 21251a1 + bfcf349 commit 69e012a

File tree

3 files changed

+138
-8
lines changed

3 files changed

+138
-8
lines changed

browser/main/NoteList/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import moment from 'moment'
77
import _ from 'lodash'
88
import ee from 'browser/main/lib/eventEmitter'
99
import dataApi from 'browser/main/lib/dataApi'
10+
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
1011
import ConfigManager from 'browser/main/lib/ConfigManager'
1112
import NoteItem from 'browser/components/NoteItem'
1213
import NoteItemSimple from 'browser/components/NoteItemSimple'
@@ -662,6 +663,10 @@ class NoteList extends React.Component {
662663
title: firstNote.title + ' ' + i18n.__('copy'),
663664
content: firstNote.content
664665
})
666+
.then((note) => {
667+
attachmentManagement.cloneAttachments(firstNote, note)
668+
return note
669+
})
665670
.then((note) => {
666671
dispatch({
667672
type: 'UPDATE_NOTE',

browser/main/lib/dataApi/attachmentManagement.js

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
4242

4343
const targetStorage = findStorage.findStorage(storageKey)
4444

45-
const inputFile = fs.createReadStream(sourceFilePath)
45+
const inputFileStream = fs.createReadStream(sourceFilePath)
4646
let destinationName
4747
if (useRandomName) {
4848
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
@@ -52,8 +52,10 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
5252
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
5353
createAttachmentDestinationFolder(targetStorage.path, noteKey)
5454
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
55-
inputFile.pipe(outputFile)
56-
resolve(destinationName)
55+
inputFileStream.pipe(outputFile)
56+
inputFileStream.on('end', () => {
57+
resolve(destinationName)
58+
})
5759
} catch (e) {
5860
return reject(e)
5961
}
@@ -149,7 +151,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
149151
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
150152
base64data += base64data.replace('+', ' ')
151153
const binaryData = new Buffer(base64data, 'base64').toString('binary')
152-
fs.writeFile(imagePath, binaryData, 'binary')
154+
fs.writeFileSync(imagePath, binaryData, 'binary')
153155
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
154156
codeEditor.insertAttachmentMd(imageMd)
155157
}
@@ -174,7 +176,7 @@ function getAttachmentsInContent (markdownContent) {
174176
* @returns {String[]} Absolute paths of the referenced attachments
175177
*/
176178
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
177-
const temp = getAttachmentsInContent(markdownContent)
179+
const temp = getAttachmentsInContent(markdownContent) || []
178180
const result = []
179181
for (const relativePath of temp) {
180182
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
@@ -198,8 +200,19 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
198200
if (fse.existsSync(src)) {
199201
fse.moveSync(src, dest)
200202
}
203+
return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey)
204+
}
205+
206+
/**
207+
* Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one
208+
* @param noteContent content that should be modified
209+
* @param oldNoteKey note key to be replaced
210+
* @param newNoteKey note key serving as a replacement
211+
* @returns {String} modified note content
212+
*/
213+
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
201214
if (noteContent) {
202-
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
215+
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
203216
}
204217
return noteContent
205218
}
@@ -268,6 +281,33 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
268281
}
269282
}
270283

284+
/**
285+
* Clones the attachments of a given note.
286+
* 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.
287+
* @param oldNote Note that is being cloned
288+
* @param newNote Clone of the note
289+
*/
290+
function cloneAttachments (oldNote, newNote) {
291+
if (newNote.type === 'MARKDOWN_NOTE') {
292+
const oldStorage = findStorage.findStorage(oldNote.storage)
293+
const newStorage = findStorage.findStorage(newNote.storage)
294+
const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || []
295+
296+
const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key)
297+
if (!sander.existsSync(destinationFolder)) {
298+
sander.mkdirSync(destinationFolder)
299+
}
300+
301+
for (const attachment of attachmentsPaths) {
302+
const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
303+
sander.copyFileSync(attachment).to(destination)
304+
}
305+
newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
306+
} else {
307+
console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs')
308+
}
309+
}
310+
271311
module.exports = {
272312
copyAttachment,
273313
fixLocalURLS,
@@ -280,6 +320,7 @@ module.exports = {
280320
deleteAttachmentFolder,
281321
deleteAttachmentsNotPresentInNote,
282322
moveAttachments,
323+
cloneAttachments,
283324
STORAGE_FOLDER_PLACEHOLDER,
284325
DESTINATION_FOLDER
285326
}

tests/dataApi/attachmentManagement.test.js

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ jest.mock('unique-slug')
88
const uniqueSlug = require('unique-slug')
99
const mdurl = require('mdurl')
1010
const fse = require('fs-extra')
11+
jest.mock('sander')
1112
const sander = require('sander')
1213

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

56+
dummyReadStream.pipe = jest.fn()
57+
dummyReadStream.on = jest.fn((event, callback) => { callback() })
5458
fs.existsSync = jest.fn()
5559
fs.existsSync.mockReturnValue(true)
56-
fs.createReadStream = jest.fn()
57-
fs.createReadStream.mockReturnValue({pipe: jest.fn()})
60+
fs.createReadStream = jest.fn(() => dummyReadStream)
5861
fs.createWriteStream = jest.fn()
5962

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

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

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

111+
dummyReadStream.pipe = jest.fn()
112+
dummyReadStream.on = jest.fn()
113+
fs.createReadStream = jest.fn(() => dummyReadStream)
103114
fs.existsSync = jest.fn()
104115
fs.existsSync.mockReturnValueOnce(true)
105116
fs.existsSync.mockReturnValueOnce(false)
@@ -383,3 +394,76 @@ it('should test that moveAttachments returns a correct modified content version'
383394
const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput)
384395
expect(actualContent).toBe(expectedOutput)
385396
})
397+
398+
it('should test that cloneAttachments modifies the content of the new note correctly', function () {
399+
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
400+
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
401+
const testInput =
402+
'Test input' +
403+
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' +
404+
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
405+
newNote.content = testInput
406+
407+
const expectedOutput =
408+
'Test input' +
409+
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'image.jpg](imageName}) \n' +
410+
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'pdf.pdf](pdf})'
411+
systemUnderTest.cloneAttachments(oldNote, newNote)
412+
413+
expect(newNote.content).toBe(expectedOutput)
414+
})
415+
416+
it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
417+
const storagePathOld = 'storagePathOld'
418+
const storagePathNew = 'storagePathNew'
419+
const dummyStorageOld = {path: storagePathOld}
420+
const dummyStorageNew = {path: storagePathNew}
421+
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'MARKDOWN_NOTE'}
422+
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'MARKDOWN_NOTE'}
423+
const testInput =
424+
'Test input' +
425+
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' +
426+
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
427+
oldNote.content = testInput
428+
newNote.content = testInput
429+
430+
const copyFileSyncResp = {to: jest.fn()}
431+
sander.copyFileSync = jest.fn()
432+
sander.copyFileSync.mockReturnValue(copyFileSyncResp)
433+
findStorage.findStorage = jest.fn()
434+
findStorage.findStorage.mockReturnValueOnce(dummyStorageOld)
435+
findStorage.findStorage.mockReturnValue(dummyStorageNew)
436+
437+
const pathAttachmentOneFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg')
438+
const pathAttachmentOneTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg')
439+
440+
const pathAttachmentTwoFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf')
441+
const pathAttachmentTwoTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf')
442+
443+
systemUnderTest.cloneAttachments(oldNote, newNote)
444+
445+
expect(findStorage.findStorage).toHaveBeenCalledWith(oldNote.storage)
446+
expect(findStorage.findStorage).toHaveBeenCalledWith(newNote.storage)
447+
expect(sander.copyFileSync).toHaveBeenCalledTimes(2)
448+
expect(copyFileSyncResp.to).toHaveBeenCalledTimes(2)
449+
expect(sander.copyFileSync.mock.calls[0][0]).toBe(pathAttachmentOneFrom)
450+
expect(copyFileSyncResp.to.mock.calls[0][0]).toBe(pathAttachmentOneTo)
451+
expect(sander.copyFileSync.mock.calls[1][0]).toBe(pathAttachmentTwoFrom)
452+
expect(copyFileSyncResp.to.mock.calls[1][0]).toBe(pathAttachmentTwoTo)
453+
})
454+
455+
it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
456+
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'SOMETHING_ELSE'}
457+
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'SOMETHING_ELSE'}
458+
const testInput = 'Test input'
459+
oldNote.content = testInput
460+
newNote.content = testInput
461+
462+
sander.copyFileSync = jest.fn()
463+
findStorage.findStorage = jest.fn()
464+
465+
systemUnderTest.cloneAttachments(oldNote, newNote)
466+
467+
expect(findStorage.findStorage).not.toHaveBeenCalled()
468+
expect(sander.copyFileSync).not.toHaveBeenCalled()
469+
})

0 commit comments

Comments
 (0)