mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
Merge pull request #1939 from ehhc/cloning_a_note_should_clone_attachments
Cloning a note should clone attachments
This commit is contained in:
@@ -7,6 +7,7 @@ import moment from 'moment'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import NoteItem from 'browser/components/NoteItem'
|
import NoteItem from 'browser/components/NoteItem'
|
||||||
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||||
@@ -662,6 +663,10 @@ class NoteList extends React.Component {
|
|||||||
title: firstNote.title + ' ' + i18n.__('copy'),
|
title: firstNote.title + ' ' + i18n.__('copy'),
|
||||||
content: firstNote.content
|
content: firstNote.content
|
||||||
})
|
})
|
||||||
|
.then((note) => {
|
||||||
|
attachmentManagement.cloneAttachments(firstNote, note)
|
||||||
|
return note
|
||||||
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
|
|||||||
|
|
||||||
const targetStorage = findStorage.findStorage(storageKey)
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
|
|
||||||
const inputFile = fs.createReadStream(sourceFilePath)
|
const inputFileStream = fs.createReadStream(sourceFilePath)
|
||||||
let destinationName
|
let destinationName
|
||||||
if (useRandomName) {
|
if (useRandomName) {
|
||||||
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
||||||
@@ -52,8 +52,10 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
|
|||||||
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
||||||
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
||||||
inputFile.pipe(outputFile)
|
inputFileStream.pipe(outputFile)
|
||||||
resolve(destinationName)
|
inputFileStream.on('end', () => {
|
||||||
|
resolve(destinationName)
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e)
|
return reject(e)
|
||||||
}
|
}
|
||||||
@@ -149,7 +151,7 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
|
|||||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||||
base64data += base64data.replace('+', ' ')
|
base64data += base64data.replace('+', ' ')
|
||||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||||
fs.writeFile(imagePath, binaryData, 'binary')
|
fs.writeFileSync(imagePath, binaryData, 'binary')
|
||||||
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
|
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
|
||||||
codeEditor.insertAttachmentMd(imageMd)
|
codeEditor.insertAttachmentMd(imageMd)
|
||||||
}
|
}
|
||||||
@@ -174,7 +176,7 @@ function getAttachmentsInContent (markdownContent) {
|
|||||||
* @returns {String[]} Absolute paths of the referenced attachments
|
* @returns {String[]} Absolute paths of the referenced attachments
|
||||||
*/
|
*/
|
||||||
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
||||||
const temp = getAttachmentsInContent(markdownContent)
|
const temp = getAttachmentsInContent(markdownContent) || []
|
||||||
const result = []
|
const result = []
|
||||||
for (const relativePath of temp) {
|
for (const relativePath of temp) {
|
||||||
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
|
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) {
|
|||||||
if (fse.existsSync(src)) {
|
if (fse.existsSync(src)) {
|
||||||
fse.moveSync(src, dest)
|
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) {
|
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
|
return noteContent
|
||||||
}
|
}
|
||||||
@@ -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 = {
|
module.exports = {
|
||||||
copyAttachment,
|
copyAttachment,
|
||||||
fixLocalURLS,
|
fixLocalURLS,
|
||||||
@@ -280,6 +320,7 @@ module.exports = {
|
|||||||
deleteAttachmentFolder,
|
deleteAttachmentFolder,
|
||||||
deleteAttachmentsNotPresentInNote,
|
deleteAttachmentsNotPresentInNote,
|
||||||
moveAttachments,
|
moveAttachments,
|
||||||
|
cloneAttachments,
|
||||||
STORAGE_FOLDER_PLACEHOLDER,
|
STORAGE_FOLDER_PLACEHOLDER,
|
||||||
DESTINATION_FOLDER
|
DESTINATION_FOLDER
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ jest.mock('unique-slug')
|
|||||||
const uniqueSlug = require('unique-slug')
|
const uniqueSlug = require('unique-slug')
|
||||||
const mdurl = require('mdurl')
|
const mdurl = require('mdurl')
|
||||||
const fse = require('fs-extra')
|
const fse = require('fs-extra')
|
||||||
|
jest.mock('sander')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
|
|
||||||
const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement')
|
const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement')
|
||||||
@@ -50,11 +51,13 @@ it('should test that copyAttachment works correctly assuming correct working of
|
|||||||
const noteKey = 'noteKey'
|
const noteKey = 'noteKey'
|
||||||
const dummyUniquePath = 'dummyPath'
|
const dummyUniquePath = 'dummyPath'
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const dummyReadStream = {}
|
||||||
|
|
||||||
|
dummyReadStream.pipe = jest.fn()
|
||||||
|
dummyReadStream.on = jest.fn((event, callback) => { callback() })
|
||||||
fs.existsSync = jest.fn()
|
fs.existsSync = jest.fn()
|
||||||
fs.existsSync.mockReturnValue(true)
|
fs.existsSync.mockReturnValue(true)
|
||||||
fs.createReadStream = jest.fn()
|
fs.createReadStream = jest.fn(() => dummyReadStream)
|
||||||
fs.createReadStream.mockReturnValue({pipe: jest.fn()})
|
|
||||||
fs.createWriteStream = jest.fn()
|
fs.createWriteStream = jest.fn()
|
||||||
|
|
||||||
findStorage.findStorage = jest.fn()
|
findStorage.findStorage = jest.fn()
|
||||||
@@ -77,7 +80,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde
|
|||||||
const noteKey = 'noteKey'
|
const noteKey = 'noteKey'
|
||||||
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
|
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
|
||||||
const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
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 = jest.fn()
|
||||||
fs.existsSync.mockReturnValueOnce(true)
|
fs.existsSync.mockReturnValueOnce(true)
|
||||||
fs.existsSync.mockReturnValueOnce(false)
|
fs.existsSync.mockReturnValueOnce(false)
|
||||||
@@ -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 () {
|
it('should test that copyAttachment don\'t uses a random file name if not intended ', function () {
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
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 = jest.fn()
|
||||||
fs.existsSync.mockReturnValueOnce(true)
|
fs.existsSync.mockReturnValueOnce(true)
|
||||||
fs.existsSync.mockReturnValueOnce(false)
|
fs.existsSync.mockReturnValueOnce(false)
|
||||||
@@ -383,3 +394,76 @@ it('should test that moveAttachments returns a correct modified content version'
|
|||||||
const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput)
|
const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput)
|
||||||
expect(actualContent).toBe(expectedOutput)
|
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' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
newNote.content = testInput
|
||||||
|
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \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' +
|
||||||
|
' \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()
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user