From 48beb184dfe3648bc7b6d4670b1c2c4d87788c42 Mon Sep 17 00:00:00 2001 From: Keyon U Date: Wed, 22 Aug 2018 12:23:45 +0800 Subject: [PATCH 01/31] Fix drop image rotate wrong Fix drag-drop image rotate wrong, as Jordan Thornquest mentioned in slack --- .../main/lib/dataApi/attachmentManagement.js | 121 +++++++++++++++--- 1 file changed, 106 insertions(+), 15 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index d1e0ab62..b40dc9f8 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -12,6 +12,82 @@ const STORAGE_FOLDER_PLACEHOLDER = ':storage' const DESTINATION_FOLDER = 'attachments' const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep) +function getImage (file) { + return new Promise((resolve) => { + const reader = new FileReader() + const img = new Image() + img.onload = () => resolve(img) + reader.onload = e => { + img.src = e.target.result + } + reader.readAsDataURL(file) + }) +} + +function getOrientation (file) { + const getData = arrayBuffer => { + const view = new DataView(arrayBuffer) + if (view.getUint16(0, false) !== 0xFFD8) return -2 + const length = view.byteLength + let offset = 2 + while (offset < length) { + const marker = view.getUint16(offset, false) + offset += 2 + if (marker === 0xFFE1) { + if (view.getUint32(offset += 2, false) !== 0x45786966) { + return -1 + } + const little = view.getUint16(offset += 6, false) === 0x4949 + offset += view.getUint32(offset + 4, little) + const tags = view.getUint16(offset, little) + offset += 2 + for (let i = 0; i < tags; i++) { + if (view.getUint16(offset + (i * 12), little) === 0x0112) { + return view.getUint16(offset + (i * 12) + 8, little) + } + } + } else if ((marker & 0xFF00) !== 0xFF00) { + break + } else { + offset += view.getUint16(offset, false) + } + } + return -1 + } + return new Promise((resolve) => { + const reader = new FileReader() + reader.onload = event => resolve(getData(event.target.result)) + reader.readAsArrayBuffer(file.slice(0, 64 * 1024)) + }) +} + +function fixRotate (file) { + return Promise.all([getImage(file), getOrientation(file)]) + .then(([img, orientation]) => { + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + if (orientation > 4 && orientation < 9) { + canvas.width = img.height + canvas.height = img.width + } else { + canvas.width = img.width + canvas.height = img.height + } + switch (orientation) { + case 2: ctx.transform(-1, 0, 0, 1, img.width, 0); break + case 3: ctx.transform(-1, 0, 0, -1, img.width, img.height); break + case 4: ctx.transform(1, 0, 0, -1, 0, img.height); break + case 5: ctx.transform(0, 1, 1, 0, 0, 0); break + case 6: ctx.transform(0, 1, -1, 0, img.height, 0); break + case 7: ctx.transform(0, -1, -1, 0, img.height, img.width); break + case 8: ctx.transform(0, -1, 1, 0, 0, img.width); break + default: break + } + ctx.drawImage(img, 0, 0) + return canvas.toDataURL() + }) +} + /** * @description * Copies a copy of an attachment to the storage folder specified by the given key and return the generated attachment name. @@ -38,26 +114,34 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr } try { - if (!fs.existsSync(sourceFilePath)) { - reject('source file does not exist') + const isBase64 = typeof sourceFilePath === 'object' && sourceFilePath.type === 'base64' + if (!fs.existsSync(sourceFilePath) && !isBase64) { + return reject('source file does not exist') } - const targetStorage = findStorage.findStorage(storageKey) - - const inputFileStream = fs.createReadStream(sourceFilePath) let destinationName if (useRandomName) { - destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}` + destinationName = `${uniqueSlug()}${path.extname(sourceFilePath.sourceFilePath || sourceFilePath)}` } else { - destinationName = path.basename(sourceFilePath) + destinationName = path.basename(sourceFilePath.sourceFilePath || sourceFilePath) } const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) createAttachmentDestinationFolder(targetStorage.path, noteKey) const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName)) - inputFileStream.pipe(outputFile) - inputFileStream.on('end', () => { - resolve(destinationName) - }) + + if (isBase64) { + const base64Data = sourceFilePath.data.replace(/^data:image\/\w+;base64,/, '') + const dataBuffer = new Buffer(base64Data, 'base64') + outputFile.write(dataBuffer, () => { + resolve(destinationName) + }) + } else { + const inputFileStream = fs.createReadStream(sourceFilePath) + inputFileStream.pipe(outputFile) + inputFileStream.on('end', () => { + resolve(destinationName) + }) + } } catch (e) { return reject(e) } @@ -137,10 +221,17 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) { const filePath = file.path const originalFileName = path.basename(filePath) const fileType = file['type'] - - copyAttachment(filePath, storageKey, noteKey).then((fileName) => { - const showPreview = fileType.startsWith('image') - const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview) + const isImage = fileType.startsWith('image') + let promise + if (isImage) { + promise = fixRotate(file).then(base64data => { + return copyAttachment({type: 'base64', data: base64data, sourceFilePath: filePath}, storageKey, noteKey) + }) + } else { + promise = copyAttachment(filePath, storageKey, noteKey) + } + promise.then((fileName) => { + const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), isImage) codeEditor.insertAttachmentMd(imageMd) }) } From 2bbcb8ca89b9d2ada132f89ee682a2924b7eaaec Mon Sep 17 00:00:00 2001 From: Keyon U Date: Wed, 22 Aug 2018 18:03:02 +0800 Subject: [PATCH 02/31] =?UTF-8?q?Add=20comments=20for=20the=20=E2=80=9Crot?= =?UTF-8?q?ate=20fix=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/lib/dataApi/attachmentManagement.js | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index b40dc9f8..912450c1 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -11,7 +11,12 @@ import i18n from 'browser/lib/i18n' const STORAGE_FOLDER_PLACEHOLDER = ':storage' const DESTINATION_FOLDER = 'attachments' const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep) - +/** + * @description + * Create a Image element to get the real size of image. + * @param {File} file the File object dropped. + * @returns {Promise} Image element created + */ function getImage (file) { return new Promise((resolve) => { const reader = new FileReader() @@ -24,29 +29,54 @@ function getImage (file) { }) } +/** + * @description + * Get the orientation info from iamges's EXIF data. + * case 1: The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side. + * case 2: The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side. + * case 3: The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side. + * case 4: The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side. + * case 5: The 0th row is the visual left-hand side of the image, and the 0th column is the visual top. + * case 6: The 0th row is the visual right-hand side of the image, and the 0th column is the visual top. + * case 7: The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom. + * case 8: The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom. + * Other: reserved + * ref: http://sylvana.net/jpegcrop/exif_orientation.html + * @param {File} file the File object dropped. + * @returns {Promise} Orientation info + */ function getOrientation (file) { const getData = arrayBuffer => { const view = new DataView(arrayBuffer) + + // Not start with SOI(Start of image) Marker return fail value if (view.getUint16(0, false) !== 0xFFD8) return -2 const length = view.byteLength let offset = 2 while (offset < length) { const marker = view.getUint16(offset, false) offset += 2 + // Loop and seed for APP1 Marker if (marker === 0xFFE1) { + // return fail value if it isn't EXIF data if (view.getUint32(offset += 2, false) !== 0x45786966) { return -1 } + // Read TIFF header, + // First 2bytes defines byte align of TIFF data. + // If it is 0x4949="II", it means "Intel" type byte align. + // If it is 0x4d4d="MM", it means "Motorola" type byte align const little = view.getUint16(offset += 6, false) === 0x4949 offset += view.getUint32(offset + 4, little) - const tags = view.getUint16(offset, little) + const tags = view.getUint16(offset, little) // Get TAG number offset += 2 for (let i = 0; i < tags; i++) { + // Loop to find Orientation TAG and return the value if (view.getUint16(offset + (i * 12), little) === 0x0112) { return view.getUint16(offset + (i * 12) + 8, little) } } - } else if ((marker & 0xFF00) !== 0xFF00) { + } else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker break } else { offset += view.getUint16(offset, false) @@ -60,7 +90,13 @@ function getOrientation (file) { reader.readAsArrayBuffer(file.slice(0, 64 * 1024)) }) } - +/** + * @description + * Rotate image file to correct direction. + * Create a canvas and draw the image with correct direction, then export to base64 format. + * @param {*} file the File object dropped. + * @return {String} Base64 encoded image. + */ function fixRotate (file) { return Promise.all([getImage(file), getOrientation(file)]) .then(([img, orientation]) => { From ac710938885f1686cdd5b7b0d1242aeabbd6a89c Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Wed, 5 Sep 2018 18:08:47 +0200 Subject: [PATCH 03/31] tags are clickable --- browser/main/Detail/TagSelect.js | 11 ++++++++++- browser/main/Detail/TagSelect.styl | 7 ++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index e251dd42..553dc680 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -99,6 +99,11 @@ class TagSelect extends React.Component { }) } + handleTagLabelClick (tag) { + const { router } = this.context + router.push(`/tags/${tag}`) + } + handleTagRemoveButtonClick (tag) { this.removeTagByCallback((value, tag) => { value.splice(value.indexOf(tag), 1) @@ -127,7 +132,7 @@ class TagSelect extends React.Component { - #{tag} + this.handleTagLabelClick(tag)}>#{tag}