Skip to content

Commit f76224b

Browse files
committed
Cloning of a note should also clone its attachments -> fixes #1904
1 parent 8afa373 commit f76224b

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
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(storage.key, firstNote, note)
668+
return note
669+
})
665670
.then((note) => {
666671
dispatch({
667672
type: 'UPDATE_NOTE',

browser/main/lib/dataApi/attachmentManagement.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,19 @@ function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
200200
if (fse.existsSync(src)) {
201201
fse.moveSync(src, dest)
202202
}
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) {
203214
if (noteContent) {
204-
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))
205216
}
206217
return noteContent
207218
}
@@ -270,6 +281,29 @@ function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey
270281
}
271282
}
272283

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 storageKey Key of the current storage
288+
* @param oldNote Note that is being cloned
289+
* @param newNote Clone of the note
290+
*/
291+
function cloneAttachments (storageKey, oldNote, newNote) {
292+
const storage = findStorage.findStorage(storageKey)
293+
const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, storage.path) || []
294+
295+
const destinationFolder = path.join(storage.path, DESTINATION_FOLDER, newNote.key)
296+
if (!sander.existsSync(destinationFolder)) {
297+
sander.mkdirSync(destinationFolder)
298+
}
299+
300+
for (const attachment of attachmentsPaths) {
301+
const destination = path.join(storage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
302+
sander.copyFileSync(attachment).to(destination)
303+
}
304+
newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
305+
}
306+
273307
module.exports = {
274308
copyAttachment,
275309
fixLocalURLS,
@@ -282,6 +316,7 @@ module.exports = {
282316
deleteAttachmentFolder,
283317
deleteAttachmentsNotPresentInNote,
284318
moveAttachments,
319+
cloneAttachments,
285320
STORAGE_FOLDER_PLACEHOLDER,
286321
DESTINATION_FOLDER
287322
}

tests/dataApi/attachmentManagement.test.js

Lines changed: 55 additions & 0 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')
@@ -393,3 +394,57 @@ it('should test that moveAttachments returns a correct modified content version'
393394
const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput)
394395
expect(actualContent).toBe(expectedOutput)
395396
})
397+
398+
it('should test that cloneAttachments modifies the content of the new note correctly', function () {
399+
const storageKey = 'storageKey'
400+
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent'}
401+
const newNote = {key: 'newNoteKey', content: 'oldNoteContent'}
402+
const testInput =
403+
'Test input' +
404+
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'image.jpg](imageName}) \n' +
405+
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
406+
newNote.content = testInput
407+
408+
const expectedOutput =
409+
'Test input' +
410+
'![' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'image.jpg](imageName}) \n' +
411+
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'pdf.pdf](pdf})'
412+
systemUnderTest.cloneAttachments(storageKey, oldNote, newNote)
413+
414+
expect(newNote.content).toBe(expectedOutput)
415+
})
416+
417+
it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
418+
const storageKey = 'storageKey'
419+
const storagePath = 'storagePath'
420+
const dummyStorage = {path: storagePath}
421+
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent'}
422+
const newNote = {key: 'newNoteKey', content: 'oldNoteContent'}
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(() => dummyStorage)
434+
435+
const pathAttachmentOneFrom = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg')
436+
const pathAttachmentOneTo = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg')
437+
438+
const pathAttachmentTwoFrom = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf')
439+
const pathAttachmentTwoTo = path.join(storagePath, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf')
440+
441+
systemUnderTest.cloneAttachments(storageKey, oldNote, newNote)
442+
443+
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
444+
expect(sander.copyFileSync).toHaveBeenCalledTimes(2)
445+
expect(copyFileSyncResp.to).toHaveBeenCalledTimes(2)
446+
expect(sander.copyFileSync.mock.calls[0][0]).toBe(pathAttachmentOneFrom)
447+
expect(copyFileSyncResp.to.mock.calls[0][0]).toBe(pathAttachmentOneTo)
448+
expect(sander.copyFileSync.mock.calls[1][0]).toBe(pathAttachmentTwoFrom)
449+
expect(copyFileSyncResp.to.mock.calls[1][0]).toBe(pathAttachmentTwoTo)
450+
})

0 commit comments

Comments
 (0)