@@ -11,6 +11,118 @@ import i18n from 'browser/lib/i18n'
11
11
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
12
12
const DESTINATION_FOLDER = 'attachments'
13
13
const PATH_SEPARATORS = escapeStringRegexp ( path . posix . sep ) + escapeStringRegexp ( path . win32 . sep )
14
+ /**
15
+ * @description
16
+ * Create a Image element to get the real size of image.
17
+ * @param {File } file the File object dropped.
18
+ * @returns {Promise<Image> } Image element created
19
+ */
20
+ 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
+ } )
30
+ }
31
+
32
+ /**
33
+ * @description
34
+ * Get the orientation info from iamges's EXIF data.
35
+ * case 1: The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
36
+ * case 2: The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
37
+ * case 3: The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
38
+ * case 4: The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
39
+ * case 5: The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
40
+ * case 6: The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
41
+ * case 7: The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
42
+ * case 8: The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
43
+ * Other: reserved
44
+ * ref: http://sylvana.net/jpegcrop/exif_orientation.html
45
+ * @param {File } file the File object dropped.
46
+ * @returns {Promise<Number> } Orientation info
47
+ */
48
+ function getOrientation ( file ) {
49
+ const getData = arrayBuffer => {
50
+ const view = new DataView ( arrayBuffer )
51
+
52
+ // Not start with SOI(Start of image) Marker return fail value
53
+ if ( view . getUint16 ( 0 , false ) !== 0xFFD8 ) return - 2
54
+ const length = view . byteLength
55
+ let offset = 2
56
+ while ( offset < length ) {
57
+ const marker = view . getUint16 ( offset , false )
58
+ offset += 2
59
+ // Loop and seed for APP1 Marker
60
+ if ( marker === 0xFFE1 ) {
61
+ // return fail value if it isn't EXIF data
62
+ if ( view . getUint32 ( offset += 2 , false ) !== 0x45786966 ) {
63
+ return - 1
64
+ }
65
+ // Read TIFF header,
66
+ // First 2bytes defines byte align of TIFF data.
67
+ // If it is 0x4949="II", it means "Intel" type byte align.
68
+ // If it is 0x4d4d="MM", it means "Motorola" type byte align
69
+ const little = view . getUint16 ( offset += 6 , false ) === 0x4949
70
+ offset += view . getUint32 ( offset + 4 , little )
71
+ const tags = view . getUint16 ( offset , little ) // Get TAG number
72
+ offset += 2
73
+ for ( let i = 0 ; i < tags ; i ++ ) {
74
+ // Loop to find Orientation TAG and return the value
75
+ if ( view . getUint16 ( offset + ( i * 12 ) , little ) === 0x0112 ) {
76
+ return view . getUint16 ( offset + ( i * 12 ) + 8 , little )
77
+ }
78
+ }
79
+ } else if ( ( marker & 0xFF00 ) !== 0xFF00 ) { // If not start with 0xFF, not a Marker
80
+ break
81
+ } else {
82
+ offset += view . getUint16 ( offset , false )
83
+ }
84
+ }
85
+ return - 1
86
+ }
87
+ return new Promise ( ( resolve ) => {
88
+ const reader = new FileReader ( )
89
+ reader . onload = event => resolve ( getData ( event . target . result ) )
90
+ reader . readAsArrayBuffer ( file . slice ( 0 , 64 * 1024 ) )
91
+ } )
92
+ }
93
+ /**
94
+ * @description
95
+ * Rotate image file to correct direction.
96
+ * Create a canvas and draw the image with correct direction, then export to base64 format.
97
+ * @param {* } file the File object dropped.
98
+ * @return {String } Base64 encoded image.
99
+ */
100
+ function fixRotate ( file ) {
101
+ return Promise . all ( [ getImage ( file ) , getOrientation ( file ) ] )
102
+ . then ( ( [ img , orientation ] ) => {
103
+ const canvas = document . createElement ( 'canvas' )
104
+ const ctx = canvas . getContext ( '2d' )
105
+ if ( orientation > 4 && orientation < 9 ) {
106
+ canvas . width = img . height
107
+ canvas . height = img . width
108
+ } else {
109
+ canvas . width = img . width
110
+ canvas . height = img . height
111
+ }
112
+ switch ( orientation ) {
113
+ case 2 : ctx . transform ( - 1 , 0 , 0 , 1 , img . width , 0 ) ; break
114
+ case 3 : ctx . transform ( - 1 , 0 , 0 , - 1 , img . width , img . height ) ; break
115
+ case 4 : ctx . transform ( 1 , 0 , 0 , - 1 , 0 , img . height ) ; break
116
+ case 5 : ctx . transform ( 0 , 1 , 1 , 0 , 0 , 0 ) ; break
117
+ case 6 : ctx . transform ( 0 , 1 , - 1 , 0 , img . height , 0 ) ; break
118
+ case 7 : ctx . transform ( 0 , - 1 , - 1 , 0 , img . height , img . width ) ; break
119
+ case 8 : ctx . transform ( 0 , - 1 , 1 , 0 , 0 , img . width ) ; break
120
+ default : break
121
+ }
122
+ ctx . drawImage ( img , 0 , 0 )
123
+ return canvas . toDataURL ( )
124
+ } )
125
+ }
14
126
15
127
/**
16
128
* @description
@@ -38,26 +150,34 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
38
150
}
39
151
40
152
try {
41
- if ( ! fs . existsSync ( sourceFilePath ) ) {
42
- reject ( 'source file does not exist' )
153
+ const isBase64 = typeof sourceFilePath === 'object' && sourceFilePath . type === 'base64'
154
+ if ( ! fs . existsSync ( sourceFilePath ) && ! isBase64 ) {
155
+ return reject ( 'source file does not exist' )
43
156
}
44
-
45
157
const targetStorage = findStorage . findStorage ( storageKey )
46
-
47
- const inputFileStream = fs . createReadStream ( sourceFilePath )
48
158
let destinationName
49
159
if ( useRandomName ) {
50
- destinationName = `${ uniqueSlug ( ) } ${ path . extname ( sourceFilePath ) } `
160
+ destinationName = `${ uniqueSlug ( ) } ${ path . extname ( sourceFilePath . sourceFilePath || sourceFilePath ) } `
51
161
} else {
52
- destinationName = path . basename ( sourceFilePath )
162
+ destinationName = path . basename ( sourceFilePath . sourceFilePath || sourceFilePath )
53
163
}
54
164
const destinationDir = path . join ( targetStorage . path , DESTINATION_FOLDER , noteKey )
55
165
createAttachmentDestinationFolder ( targetStorage . path , noteKey )
56
166
const outputFile = fs . createWriteStream ( path . join ( destinationDir , destinationName ) )
57
- inputFileStream . pipe ( outputFile )
58
- inputFileStream . on ( 'end' , ( ) => {
59
- resolve ( destinationName )
60
- } )
167
+
168
+ if ( isBase64 ) {
169
+ const base64Data = sourceFilePath . data . replace ( / ^ d a t a : i m a g e \/ \w + ; b a s e 6 4 , / , '' )
170
+ const dataBuffer = new Buffer ( base64Data , 'base64' )
171
+ outputFile . write ( dataBuffer , ( ) => {
172
+ resolve ( destinationName )
173
+ } )
174
+ } else {
175
+ const inputFileStream = fs . createReadStream ( sourceFilePath )
176
+ inputFileStream . pipe ( outputFile )
177
+ inputFileStream . on ( 'end' , ( ) => {
178
+ resolve ( destinationName )
179
+ } )
180
+ }
61
181
} catch ( e ) {
62
182
return reject ( e )
63
183
}
@@ -137,10 +257,17 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
137
257
const filePath = file . path
138
258
const originalFileName = path . basename ( filePath )
139
259
const fileType = file [ 'type' ]
140
-
141
- copyAttachment ( filePath , storageKey , noteKey ) . then ( ( fileName ) => {
142
- const showPreview = fileType . startsWith ( 'image' )
143
- const imageMd = generateAttachmentMarkdown ( originalFileName , path . join ( STORAGE_FOLDER_PLACEHOLDER , noteKey , fileName ) , showPreview )
260
+ const isImage = fileType . startsWith ( 'image' )
261
+ let promise
262
+ if ( isImage ) {
263
+ promise = fixRotate ( file ) . then ( base64data => {
264
+ return copyAttachment ( { type : 'base64' , data : base64data , sourceFilePath : filePath } , storageKey , noteKey )
265
+ } )
266
+ } else {
267
+ promise = copyAttachment ( filePath , storageKey , noteKey )
268
+ }
269
+ promise . then ( ( fileName ) => {
270
+ const imageMd = generateAttachmentMarkdown ( originalFileName , path . join ( STORAGE_FOLDER_PLACEHOLDER , noteKey , fileName ) , isImage )
144
271
codeEditor . insertAttachmentMd ( imageMd )
145
272
} )
146
273
}
0 commit comments