-
Notifications
You must be signed in to change notification settings - Fork 581
Feature: Import from Google Keep #2015
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
51d2d60
aa40cb3
454f1e7
ed42852
434c1b3
2172c21
84ae895
84b49ee
f8e3e0e
72c3e34
eb8641f
7bb6b6b
4d3dc0d
3bc9544
f526f32
bd9425b
c6c1bf7
bb38e0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { EventEmitter } from 'events'; | ||
import { endsWith, isEmpty, get, has } from 'lodash'; | ||
import JSZip from 'jszip'; | ||
|
||
import CoreImporter from '../'; | ||
|
||
let fs = null; | ||
const isElectron = has(window, 'process.type'); | ||
if (isElectron) { | ||
fs = __non_webpack_require__('fs'); // eslint-disable-line no-undef | ||
} | ||
|
||
class GoogleKeepImporter extends EventEmitter { | ||
constructor({ noteBucket, tagBucket, options }) { | ||
super(); | ||
this.noteBucket = noteBucket; | ||
this.tagBucket = tagBucket; | ||
this.options = options; | ||
} | ||
|
||
importNotes = filesArray => { | ||
if (isEmpty(filesArray)) { | ||
this.emit('status', 'error', 'No file to import.'); | ||
return; | ||
} | ||
|
||
const coreImporter = new CoreImporter({ | ||
noteBucket: this.noteBucket, | ||
tagBucket: this.tagBucket, | ||
}); | ||
|
||
let importedNoteCount = 0; | ||
|
||
const importJsonString = jsonString => { | ||
// note: If importing the note fails, it is silently ignored by the | ||
// promise below. This is okay since the warning message would be hidden | ||
// by the next progress update anyway. | ||
const importedNote = JSON.parse(jsonString); | ||
mgunyho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if ( | ||
!importedNote.title && | ||
!importedNote.textContent && | ||
!importedNote.listContent | ||
) { | ||
// empty note, skip | ||
return; | ||
} | ||
|
||
const title = importedNote.title; | ||
|
||
const importedContent = importedNote.listContent | ||
? importedNote.listContent // Note has checkboxes, no text content | ||
.map(item => `- [${item.isChecked ? 'x' : ' '}] ${item.text}`) | ||
.join('\n') | ||
: importedNote.textContent; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. interesting. a note can only either be a list or some text? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, in Keep, you can "show checkboxes", which turns the whole note into a checklist, and as far as I can tell there's no way to have regular text then. |
||
|
||
const textContent = title | ||
? `${title}\n\n${importedContent}` | ||
: importedContent; | ||
|
||
return coreImporter | ||
.importNote( | ||
{ | ||
content: textContent, | ||
// Keep doesn't store the creation date... | ||
creationDate: importedNote.userEditedTimestampUsec / 1e6, | ||
modificationDate: importedNote.userEditedTimestampUsec / 1e6, | ||
pinned: importedNote.isPinned, | ||
tags: get(importedNote, 'labels', []).map(item => item.name), | ||
}, | ||
{ ...this.options, isTrashed: importedNote.isTrashed } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it were me I might suggest sending archived notes to the trash or add a tag to them - trash is generally safe but someone could accidentally "empty the trash" and wipe out their archive if, on the other hand, they import their archive into the "All Notes" section then they might get more notes in the list than they expect. maybe we just need a setting…
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is how I would do it as well. I don't know how to add a (importer-specific) setting to the dialog. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will try to play around with it this week and see if it wouldn't be too hard. if not, maybe adding |
||
) | ||
.then(() => { | ||
importedNoteCount++; | ||
this.emit('status', 'progress', importedNoteCount); | ||
}); | ||
}; | ||
|
||
const importZipFile = fileData => | ||
JSZip.loadAsync(fileData).then(zip => { | ||
const promises = zip | ||
.file(/.*\/Keep\/.*\.json/) | ||
.map(zipObj => zipObj.async('string').then(importJsonString)); | ||
return Promise.all(promises); | ||
}); | ||
|
||
const promises = filesArray.map(file => | ||
fs.promises | ||
mgunyho marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.readFile(file.path) | ||
.then(data => { | ||
if (endsWith(file.name.toLowerCase(), '.zip')) { | ||
return importZipFile(data); | ||
} else if (endsWith(file.name.toLowerCase(), '.json')) { | ||
// The data is a string, import it directly | ||
return importJsonString(data); | ||
} else { | ||
this.emit( | ||
'status', | ||
'error', | ||
`Invalid file extension: ${file.name}` | ||
); | ||
} | ||
}) | ||
.catch(err => { | ||
this.emit('status', 'error', `Error reading file ${file.path}`); | ||
}) | ||
); | ||
|
||
return Promise.all(promises).then(() => { | ||
this.emit('status', 'complete', importedNoteCount); | ||
}); | ||
}; | ||
} | ||
|
||
export default GoogleKeepImporter; |
Uh oh!
There was an error while loading. Please reload this page.