Skip to content

Commit 8b8d915

Browse files
authored
Merge pull request #2642 from daiyam/drop-browser-image
handle all dropped images
2 parents 54de57e + e0d9cf7 commit 8b8d915

File tree

2 files changed

+234
-34
lines changed

2 files changed

+234
-34
lines changed

browser/main/lib/dataApi/attachmentManagement.js

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const mdurl = require('mdurl')
66
const fse = require('fs-extra')
77
const escapeStringRegexp = require('escape-string-regexp')
88
const sander = require('sander')
9+
const url = require('url')
910
import i18n from 'browser/lib/i18n'
1011

1112
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
@@ -18,15 +19,23 @@ const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(
1819
* @returns {Promise<Image>} Image element created
1920
*/
2021
function getImage (file) {
21-
return new Promise((resolve) => {
22-
const reader = new FileReader()
23-
const img = new Image()
24-
img.onload = () => resolve(img)
25-
reader.onload = e => {
26-
img.src = e.target.result
27-
}
28-
reader.readAsDataURL(file)
29-
})
22+
if (_.isString(file)) {
23+
return new Promise(resolve => {
24+
const img = new Image()
25+
img.onload = () => resolve(img)
26+
img.src = file
27+
})
28+
} else {
29+
return new Promise(resolve => {
30+
const reader = new FileReader()
31+
const img = new Image()
32+
img.onload = () => resolve(img)
33+
reader.onload = e => {
34+
img.src = e.target.result
35+
}
36+
reader.readAsDataURL(file)
37+
})
38+
}
3039
}
3140

3241
/**
@@ -151,23 +160,28 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
151160

152161
try {
153162
const isBase64 = typeof sourceFilePath === 'object' && sourceFilePath.type === 'base64'
154-
if (!fs.existsSync(sourceFilePath) && !isBase64) {
163+
if (!isBase64 && !fs.existsSync(sourceFilePath)) {
155164
return reject('source file does not exist')
156165
}
157-
const targetStorage = findStorage.findStorage(storageKey)
166+
167+
const sourcePath = sourceFilePath.sourceFilePath || sourceFilePath
168+
const sourceURL = url.parse(/^\w+:\/\//.test(sourcePath) ? sourcePath : 'file:///' + sourcePath)
169+
158170
let destinationName
159171
if (useRandomName) {
160-
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath.sourceFilePath || sourceFilePath)}`
172+
destinationName = `${uniqueSlug()}${path.extname(sourceURL.pathname) || '.png'}`
161173
} else {
162-
destinationName = path.basename(sourceFilePath.sourceFilePath || sourceFilePath)
174+
destinationName = path.basename(sourceURL.pathname)
163175
}
176+
177+
const targetStorage = findStorage.findStorage(storageKey)
164178
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
165179
createAttachmentDestinationFolder(targetStorage.path, noteKey)
166180
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
167181

168182
if (isBase64) {
169183
const base64Data = sourceFilePath.data.replace(/^data:image\/\w+;base64,/, '')
170-
const dataBuffer = new Buffer(base64Data, 'base64')
184+
const dataBuffer = Buffer.from(base64Data, 'base64')
171185
outputFile.write(dataBuffer, () => {
172186
resolve(destinationName)
173187
})
@@ -261,23 +275,69 @@ function generateAttachmentMarkdown (fileName, path, showPreview) {
261275
* @param {Event} dropEvent DropEvent
262276
*/
263277
function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
264-
const file = dropEvent.dataTransfer.files[0]
265-
const filePath = file.path
266-
const originalFileName = path.basename(filePath)
267-
const fileType = file['type']
268-
const isImage = fileType.startsWith('image')
269-
const isGif = fileType.endsWith('gif')
270278
let promise
271-
if (isImage && !isGif) {
272-
promise = fixRotate(file).then(base64data => {
273-
return copyAttachment({type: 'base64', data: base64data, sourceFilePath: filePath}, storageKey, noteKey)
274-
})
279+
if (dropEvent.dataTransfer.files.length > 0) {
280+
promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => {
281+
if (file.type.startsWith('image')) {
282+
if (file.type === 'image/gif' || file.type === 'image/svg+xml') {
283+
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
284+
fileName,
285+
title: path.basename(file.path),
286+
isImage: true
287+
}))
288+
} else {
289+
return fixRotate(file)
290+
.then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey)
291+
.then(fileName => ({
292+
fileName,
293+
title: path.basename(file.path),
294+
isImage: true
295+
}))
296+
)
297+
}
298+
} else {
299+
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
300+
fileName,
301+
title: path.basename(file.path),
302+
isImage: false
303+
}))
304+
}
305+
}))
275306
} else {
276-
promise = copyAttachment(filePath, storageKey, noteKey)
307+
let imageURL = dropEvent.dataTransfer.getData('text/plain')
308+
309+
if (!imageURL) {
310+
const match = /<img[^>]*[\s"']src="([^"]+)"/.exec(dropEvent.dataTransfer.getData('text/html'))
311+
if (match) {
312+
imageURL = match[1]
313+
}
314+
}
315+
316+
if (!imageURL) {
317+
return
318+
}
319+
320+
promise = Promise.all([getImage(imageURL)
321+
.then(image => {
322+
const canvas = document.createElement('canvas')
323+
const context = canvas.getContext('2d')
324+
canvas.width = image.width
325+
canvas.height = image.height
326+
context.drawImage(image, 0, 0)
327+
328+
return copyAttachment({type: 'base64', data: canvas.toDataURL(), sourceFilePath: imageURL}, storageKey, noteKey)
329+
})
330+
.then(fileName => ({
331+
fileName,
332+
title: imageURL,
333+
isImage: true
334+
}))])
277335
}
278-
promise.then((fileName) => {
279-
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), isImage)
280-
codeEditor.insertAttachmentMd(imageMd)
336+
337+
promise.then(files => {
338+
const attachments = files.filter(file => !!file).map(file => generateAttachmentMarkdown(file.title, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, file.fileName), file.isImage))
339+
340+
codeEditor.insertAttachmentMd(attachments.join('\n'))
281341
})
282342
}
283343

tests/dataApi/attachmentManagement.test.js

Lines changed: 146 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ it('should test that copyAttachment should throw an error if sourcePath dosen\'t
3838
fs.existsSync = jest.fn()
3939
fs.existsSync.mockReturnValue(false)
4040

41-
systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then(() => {}, error => {
41+
return systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then(() => {}, error => {
4242
expect(error).toBe('source file does not exist')
4343
expect(fs.existsSync).toHaveBeenCalledWith('path')
4444
})
@@ -64,7 +64,7 @@ it('should test that copyAttachment works correctly assuming correct working of
6464
findStorage.findStorage.mockReturnValue(dummyStorage)
6565
uniqueSlug.mockReturnValue(dummyUniquePath)
6666

67-
systemUnderTest.copyAttachment(sourcePath, storageKey, noteKey).then(
67+
return systemUnderTest.copyAttachment(sourcePath, storageKey, noteKey).then(
6868
function (newFileName) {
6969
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
7070
expect(fs.createReadStream).toHaveBeenCalledWith(sourcePath)
@@ -83,7 +83,7 @@ it('should test that copyAttachment creates a new folder if the attachment folde
8383
const dummyReadStream = {}
8484

8585
dummyReadStream.pipe = jest.fn()
86-
dummyReadStream.on = jest.fn()
86+
dummyReadStream.on = jest.fn((event, callback) => { callback() })
8787
fs.createReadStream = jest.fn(() => dummyReadStream)
8888
fs.existsSync = jest.fn()
8989
fs.existsSync.mockReturnValueOnce(true)
@@ -95,7 +95,7 @@ it('should test that copyAttachment creates a new folder if the attachment folde
9595
findStorage.findStorage.mockReturnValue(dummyStorage)
9696
uniqueSlug.mockReturnValue('dummyPath')
9797

98-
systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then(
98+
return systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey').then(
9999
function () {
100100
expect(fs.existsSync).toHaveBeenCalledWith(attachmentFolderPath)
101101
expect(fs.mkdirSync).toHaveBeenCalledWith(attachmentFolderPath)
@@ -109,7 +109,7 @@ it('should test that copyAttachment don\'t uses a random file name if not intend
109109
const dummyReadStream = {}
110110

111111
dummyReadStream.pipe = jest.fn()
112-
dummyReadStream.on = jest.fn()
112+
dummyReadStream.on = jest.fn((event, callback) => { callback() })
113113
fs.createReadStream = jest.fn(() => dummyReadStream)
114114
fs.existsSync = jest.fn()
115115
fs.existsSync.mockReturnValueOnce(true)
@@ -120,12 +120,152 @@ it('should test that copyAttachment don\'t uses a random file name if not intend
120120
findStorage.findStorage.mockReturnValue(dummyStorage)
121121
uniqueSlug.mockReturnValue('dummyPath')
122122

123-
systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey', false).then(
123+
return systemUnderTest.copyAttachment('path', 'storageKey', 'noteKey', false).then(
124124
function (newFileName) {
125125
expect(newFileName).toBe('path')
126126
})
127127
})
128128

129+
it('should test that copyAttachment with url (with extension, without query)', function () {
130+
const dummyStorage = {path: 'dummyStoragePath'}
131+
132+
const dummyReadStream = {
133+
pipe: jest.fn(),
134+
on: jest.fn((event, callback) => { callback() })
135+
}
136+
fs.createReadStream = jest.fn(() => dummyReadStream)
137+
138+
const dummyWriteStream = {
139+
write: jest.fn((data, callback) => { callback() })
140+
}
141+
fs.createWriteStream = jest.fn(() => dummyWriteStream)
142+
143+
fs.existsSync = jest.fn()
144+
fs.existsSync.mockReturnValueOnce(true)
145+
fs.existsSync.mockReturnValueOnce(false)
146+
fs.mkdirSync = jest.fn()
147+
148+
findStorage.findStorage = jest.fn()
149+
findStorage.findStorage.mockReturnValue(dummyStorage)
150+
uniqueSlug.mockReturnValue('dummyPath')
151+
152+
const sourcePath = {
153+
sourceFilePath: 'http://www.foo.bar/baz/qux.jpg',
154+
type: 'base64',
155+
data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl'
156+
}
157+
158+
return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then(
159+
function (newFileName) {
160+
expect(newFileName).toBe('dummyPath.jpg')
161+
})
162+
})
163+
164+
it('should test that copyAttachment with url (with extension, with query)', function () {
165+
const dummyStorage = {path: 'dummyStoragePath'}
166+
167+
const dummyReadStream = {
168+
pipe: jest.fn(),
169+
on: jest.fn((event, callback) => { callback() })
170+
}
171+
fs.createReadStream = jest.fn(() => dummyReadStream)
172+
173+
const dummyWriteStream = {
174+
write: jest.fn((data, callback) => { callback() })
175+
}
176+
fs.createWriteStream = jest.fn(() => dummyWriteStream)
177+
178+
fs.existsSync = jest.fn()
179+
fs.existsSync.mockReturnValueOnce(true)
180+
fs.existsSync.mockReturnValueOnce(false)
181+
fs.mkdirSync = jest.fn()
182+
183+
findStorage.findStorage = jest.fn()
184+
findStorage.findStorage.mockReturnValue(dummyStorage)
185+
uniqueSlug.mockReturnValue('dummyPath')
186+
187+
const sourcePath = {
188+
sourceFilePath: 'http://www.foo.bar/baz/qux.jpg?h=1080',
189+
type: 'base64',
190+
data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl'
191+
}
192+
193+
return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then(
194+
function (newFileName) {
195+
expect(newFileName).toBe('dummyPath.jpg')
196+
})
197+
})
198+
199+
it('should test that copyAttachment with url (without extension, without query)', function () {
200+
const dummyStorage = {path: 'dummyStoragePath'}
201+
202+
const dummyReadStream = {
203+
pipe: jest.fn(),
204+
on: jest.fn((event, callback) => { callback() })
205+
}
206+
fs.createReadStream = jest.fn(() => dummyReadStream)
207+
208+
const dummyWriteStream = {
209+
write: jest.fn((data, callback) => { callback() })
210+
}
211+
fs.createWriteStream = jest.fn(() => dummyWriteStream)
212+
213+
fs.existsSync = jest.fn()
214+
fs.existsSync.mockReturnValueOnce(true)
215+
fs.existsSync.mockReturnValueOnce(false)
216+
fs.mkdirSync = jest.fn()
217+
218+
findStorage.findStorage = jest.fn()
219+
findStorage.findStorage.mockReturnValue(dummyStorage)
220+
uniqueSlug.mockReturnValue('dummyPath')
221+
222+
const sourcePath = {
223+
sourceFilePath: 'http://www.foo.bar/baz/qux',
224+
type: 'base64',
225+
data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl'
226+
}
227+
228+
return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then(
229+
function (newFileName) {
230+
expect(newFileName).toBe('dummyPath.png')
231+
})
232+
})
233+
234+
it('should test that copyAttachment with url (without extension, with query)', function () {
235+
const dummyStorage = {path: 'dummyStoragePath'}
236+
237+
const dummyReadStream = {
238+
pipe: jest.fn(),
239+
on: jest.fn((event, callback) => { callback() })
240+
}
241+
fs.createReadStream = jest.fn(() => dummyReadStream)
242+
243+
const dummyWriteStream = {
244+
write: jest.fn((data, callback) => { callback() })
245+
}
246+
fs.createWriteStream = jest.fn(() => dummyWriteStream)
247+
248+
fs.existsSync = jest.fn()
249+
fs.existsSync.mockReturnValueOnce(true)
250+
fs.existsSync.mockReturnValueOnce(false)
251+
fs.mkdirSync = jest.fn()
252+
253+
findStorage.findStorage = jest.fn()
254+
findStorage.findStorage.mockReturnValue(dummyStorage)
255+
uniqueSlug.mockReturnValue('dummyPath')
256+
257+
const sourcePath = {
258+
sourceFilePath: 'http://www.foo.bar/baz/qux?h=1080',
259+
type: 'base64',
260+
data: 'data:image/jpeg;base64,Ym9vc3Rub3Rl'
261+
}
262+
263+
return systemUnderTest.copyAttachment(sourcePath, 'storageKey', 'noteKey').then(
264+
function (newFileName) {
265+
expect(newFileName).toBe('dummyPath.png')
266+
})
267+
})
268+
129269
it('should replace the all ":storage" path with the actual storage path', function () {
130270
const storageFolder = systemUnderTest.DESTINATION_FOLDER
131271
const noteKey = '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c'

0 commit comments

Comments
 (0)